diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 82446ac5b4..c1e5575a52 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -36,11 +36,3 @@ println!("My super cool code."); ``` - -## Migration Guide - -> This section is optional. If there are no breaking changes, you can delete this section. - -- If this PR is a breaking change (relative to the last release of Bevy), describe how a user might need to migrate their code to support these changes -- Simply adding new functionality is not a breaking change. -- Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change. diff --git a/.github/workflows/action-on-PR-labeled.yml b/.github/workflows/action-on-PR-labeled.yml index 9887494a48..9e5835c1f7 100644 --- a/.github/workflows/action-on-PR-labeled.yml +++ b/.github/workflows/action-on-PR-labeled.yml @@ -12,19 +12,63 @@ permissions: pull-requests: 'write' jobs: - comment-on-breaking-change-label: + comment-on-migration-guide-label: runs-on: ubuntu-latest - if: github.event.label.name == 'M-Needs-Migration-Guide' && !contains(github.event.pull_request.body, '## Migration Guide') + if: github.event.label.name == 'M-Needs-Migration-Guide' steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 2 + - name: Get changes + id: get_changes + shell: bash {0} + run: | + git fetch --depth=1 origin $BASE_SHA + git diff --exit-code $BASE_SHA $HEAD_SHA -- ./release-content/migration-guides + echo "found_changes=$?" >> $GITHUB_OUTPUT + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} - uses: actions/github-script@v7 + if: steps.get_changes.outputs.found_changes == '0' with: script: | await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `It looks like your PR is a breaking change, but you didn't provide a migration guide. + body: `It looks like your PR is a breaking change, but **you didn't provide a migration guide**. - Could you add some context on what users should update when this change get released in a new version of Bevy? - It will be used to help writing the migration guide for the version. Putting it after a \`## Migration Guide\` will help it get automatically picked up by our tooling.` + Please review the [instructions for writing migration guides](https://github.com/bevyengine/bevy/tree/main/release-content/migration_guides.md), then expand or revise the content in the [migration guides directory](https://github.com/bevyengine/bevy/tree/main/release-content/migration-guides) to reflect your changes.` + }) + comment-on-release-note-label: + runs-on: ubuntu-latest + if: github.event.label.name == 'M-Needs-Release-Note' + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 2 + - name: Get changes + id: get_changes + shell: bash {0} + run: | + git fetch --depth=1 origin $BASE_SHA + git diff --exit-code $BASE_SHA $HEAD_SHA -- ./release-content/release-notes + echo "found_changes=$?" >> $GITHUB_OUTPUT + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + - uses: actions/github-script@v7 + if: steps.get_changes.outputs.found_changes == '0' + with: + script: | + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `It looks like your PR has been selected for a highlight in the next release blog post, but **you didn't provide a release note**. + + Please review the [instructions for writing release notes](https://github.com/bevyengine/bevy/tree/main/release-content/release_notes.md), then expand or revise the content in the [release notes directory](https://github.com/bevyengine/bevy/tree/main/release-content/release_notes) to showcase your changes.` }) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f82c0d0ea..37db848558 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ env: # If nightly is breaking CI, modify this variable to target a specific nightly version. NIGHTLY_TOOLCHAIN: nightly RUSTFLAGS: "-D warnings" + BINSTALL_VERSION: "v1.12.3" concurrency: group: ${{github.workflow}}-${{github.ref}} @@ -271,9 +272,9 @@ jobs: timeout-minutes: 30 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: cargo-bins/cargo-binstall@v1.12.3 - name: Install taplo - run: cargo install taplo-cli --locked + run: cargo binstall taplo-cli@0.9.3 --locked - name: Run Taplo id: taplo run: taplo fmt --check --diff @@ -292,7 +293,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.30.2 + uses: crate-ci/typos@v1.32.0 - name: Typos info if: failure() run: | diff --git a/Cargo.toml b/Cargo.toml index 8cfa6fba93..a07822deec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" repository = "https://github.com/bevyengine/bevy" documentation = "https://docs.rs/bevy" -rust-version = "1.85.0" +rust-version = "1.86.0" [workspace] resolver = "2" @@ -133,6 +133,7 @@ default = [ "bevy_audio", "bevy_color", "bevy_core_pipeline", + "bevy_anti_aliasing", "bevy_gilrs", "bevy_gizmos", "bevy_gltf", @@ -213,6 +214,13 @@ bevy_core_pipeline = [ "bevy_render", ] +# Provides various anti aliasing solutions +bevy_anti_aliasing = [ + "bevy_internal/bevy_anti_aliasing", + "bevy_asset", + "bevy_render", +] + # Adds gamepad support bevy_gilrs = ["bevy_internal/bevy_gilrs"] @@ -225,6 +233,7 @@ bevy_pbr = [ "bevy_asset", "bevy_render", "bevy_core_pipeline", + "bevy_anti_aliasing", ] # Provides picking functionality @@ -242,6 +251,7 @@ bevy_sprite = [ "bevy_render", "bevy_core_pipeline", "bevy_color", + "bevy_anti_aliasing", ] # Provides text functionality @@ -254,6 +264,7 @@ bevy_ui = [ "bevy_text", "bevy_sprite", "bevy_color", + "bevy_anti_aliasing", ] # Windowing layer @@ -537,7 +548,7 @@ rand_chacha = "0.3.1" ron = "0.8.0" flate2 = "1.0" serde = { version = "1", features = ["derive"] } -serde_json = "1" +serde_json = "1.0.140" bytemuck = "1.7" bevy_render = { path = "crates/bevy_render", version = "0.16.0-dev", default-features = false } # The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself. @@ -558,7 +569,7 @@ hyper = { version = "1", features = ["server", "http1"] } http-body-util = "0.1" anyhow = "1" macro_rules_attribute = "0.2" -accesskit = "0.17" +accesskit = "0.19" nonmax = "0.5" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] @@ -571,6 +582,17 @@ ureq = { version = "3.0.8", features = ["json"] } wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["Window"] } +[[example]] +name = "context_menu" +path = "examples/usages/context_menu.rs" +doc-scrape-examples = true + +[package.metadata.example.context_menu] +name = "Context Menu" +description = "Example of a context menu" +category = "Usage" +wasm = true + [[example]] name = "hello_world" path = "examples/hello_world.rs" @@ -1578,6 +1600,7 @@ wasm = true name = "headless" path = "examples/app/headless.rs" doc-scrape-examples = true +required-features = ["bevy_log"] [package.metadata.example.headless] name = "Headless" @@ -2208,14 +2231,14 @@ category = "ECS (Entity Component System)" wasm = false [[example]] -name = "fallible_systems" -path = "examples/ecs/fallible_systems.rs" +name = "error_handling" +path = "examples/ecs/error_handling.rs" doc-scrape-examples = true required-features = ["bevy_mesh_picking_backend"] -[package.metadata.example.fallible_systems] -name = "Fallible Systems" -description = "Systems that return results to handle errors" +[package.metadata.example.error_handling] +name = "Error handling" +description = "How to return and handle errors across the ECS" category = "ECS (Entity Component System)" wasm = false @@ -2289,6 +2312,17 @@ description = "Pipe the output of one system into a second, allowing you to hand category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "state_scoped" +path = "examples/ecs/state_scoped.rs" +doc-scrape-examples = true + +[package.metadata.example.state_scoped] +name = "State Scoped" +description = "Shows how to spawn entities that are automatically despawned either when entering or exiting specific game states." +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "system_closure" path = "examples/ecs/system_closure.rs" @@ -3328,6 +3362,17 @@ description = "Illustrates creating and updating text" category = "UI (User Interface)" wasm = true +[[example]] +name = "text_background_colors" +path = "examples/ui/text_background_colors.rs" +doc-scrape-examples = true + +[package.metadata.example.text_background_colors] +name = "Text Background Colors" +description = "Demonstrates text background colors" +category = "UI (User Interface)" +wasm = true + [[example]] name = "text_debug" path = "examples/ui/text_debug.rs" @@ -3384,6 +3429,28 @@ description = "An example for CSS Grid layout" category = "UI (User Interface)" wasm = true +[[example]] +name = "gradients" +path = "examples/ui/gradients.rs" +doc-scrape-examples = true + +[package.metadata.example.gradients] +name = "Gradients" +description = "An example demonstrating gradients" +category = "UI (User Interface)" +wasm = true + +[[example]] +name = "stacked_gradients" +path = "examples/ui/stacked_gradients.rs" +doc-scrape-examples = true + +[package.metadata.example.stacked_gradients] +name = "Stacked Gradients" +description = "An example demonstrating stacked gradients" +category = "UI (User Interface)" +wasm = true + [[example]] name = "scroll" path = "examples/ui/scroll.rs" @@ -3483,6 +3550,17 @@ description = "An example for debugging viewport coordinates" category = "UI (User Interface)" wasm = true +[[example]] +name = "viewport_node" +path = "examples/ui/viewport_node.rs" +doc-scrape-examples = true + +[package.metadata.example.viewport_node] +name = "Viewport Node" +description = "Demonstrates how to create a viewport node with picking support" +category = "UI (User Interface)" +wasm = true + # Window [[example]] name = "clear_color" @@ -4312,3 +4390,25 @@ name = "`no_std` Compatible Library" description = "Example library compatible with `std` and `no_std` targets" category = "Embedded" wasm = true + +[[example]] +name = "extended_material_bindless" +path = "examples/shader/extended_material_bindless.rs" +doc-scrape-examples = true + +[package.metadata.example.extended_material_bindless] +name = "Extended Bindless Material" +description = "Demonstrates bindless `ExtendedMaterial`" +category = "Shaders" +wasm = false + +[[example]] +name = "cooldown" +path = "examples/usage/cooldown.rs" +doc-scrape-examples = true + +[package.metadata.example.cooldown] +name = "Cooldown" +description = "Example for cooldown on button clicks" +category = "Usage" +wasm = true diff --git a/README.md b/README.md index be1bcf6bfe..1daeadda5d 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ To draw a window with standard functionality enabled, use: ```rust use bevy::prelude::*; -fn main(){ +fn main() { App::new() .add_plugins(DefaultPlugins) .run(); diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index e768a7b149..477bee3054 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -5,7 +5,7 @@ ), }, entities: { - 4294967296: ( + 4294967297: ( components: { "bevy_ecs::name::Name": "joe", "bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)), @@ -23,7 +23,7 @@ ), }, ), - 4294967297: ( + 4294967298: ( components: { "scene::ComponentA": ( x: 3.0, @@ -32,4 +32,4 @@ }, ), }, -) \ No newline at end of file +) diff --git a/assets/shaders/custom_material.wesl b/assets/shaders/custom_material.wesl index 2fd32fc923..5113e1cbe0 100644 --- a/assets/shaders/custom_material.wesl +++ b/assets/shaders/custom_material.wesl @@ -6,7 +6,8 @@ struct VertexOutput { } struct CustomMaterial { - time: f32, + // Needed for 16-bit alignment on WebGL2 + time: vec4, } @group(2) @binding(0) var material: CustomMaterial; @@ -15,5 +16,5 @@ struct CustomMaterial { fn fragment( mesh: VertexOutput, ) -> @location(0) vec4 { - return make_polka_dots(mesh.uv, material.time); -} \ No newline at end of file + return make_polka_dots(mesh.uv, material.time.x); +} diff --git a/assets/shaders/extended_material_bindless.wgsl b/assets/shaders/extended_material_bindless.wgsl new file mode 100644 index 0000000000..f8650b0da7 --- /dev/null +++ b/assets/shaders/extended_material_bindless.wgsl @@ -0,0 +1,107 @@ +// The shader that goes with `extended_material_bindless.rs`. +// +// This code demonstrates how to write shaders that are compatible with both +// bindless and non-bindless mode. See the `#ifdef BINDLESS` blocks. + +#import bevy_pbr::{ + forward_io::{FragmentOutput, VertexOutput}, + mesh_bindings::mesh, + pbr_fragment::pbr_input_from_standard_material, + pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing}, +} +#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d} + +#ifdef BINDLESS +#import bevy_pbr::pbr_bindings::{material_array, material_indices} +#else // BINDLESS +#import bevy_pbr::pbr_bindings::material +#endif // BINDLESS + +// Stores the indices of the bindless resources in the bindless resource arrays, +// for the `ExampleBindlessExtension` fields. +struct ExampleBindlessExtendedMaterialIndices { + // The index of the `ExampleBindlessExtendedMaterial` data in + // `example_extended_material`. + material: u32, + // The index of the texture we're going to modulate the base color with in + // the `bindless_textures_2d` array. + modulate_texture: u32, + // The index of the sampler we're going to sample the modulated texture with + // in the `bindless_samplers_filtering` array. + modulate_texture_sampler: u32, +} + +// Plain data associated with this example material. +struct ExampleBindlessExtendedMaterial { + // The color that we multiply the base color, base color texture, and + // modulated texture with. + modulate_color: vec4, +} + +#ifdef BINDLESS + +// The indices of the bindless resources in the bindless resource arrays, for +// the `ExampleBindlessExtension` fields. +@group(2) @binding(100) var example_extended_material_indices: + array; +// An array that holds the `ExampleBindlessExtendedMaterial` plain old data, +// indexed by `ExampleBindlessExtendedMaterialIndices.material`. +@group(2) @binding(101) var example_extended_material: + array; + +#else // BINDLESS + +// In non-bindless mode, we simply use a uniform for the plain old data. +@group(2) @binding(50) var example_extended_material: ExampleBindlessExtendedMaterial; +@group(2) @binding(51) var modulate_texture: texture_2d; +@group(2) @binding(52) var modulate_sampler: sampler; + +#endif // BINDLESS + +@fragment +fn fragment( + in: VertexOutput, + @builtin(front_facing) is_front: bool, +) -> FragmentOutput { +#ifdef BINDLESS + // Fetch the material slot. We'll use this in turn to fetch the bindless + // indices from `example_extended_material_indices`. + let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; +#endif // BINDLESS + + // Generate a `PbrInput` struct from the `StandardMaterial` bindings. + var pbr_input = pbr_input_from_standard_material(in, is_front); + + // Calculate the UV for the texture we're about to sample. +#ifdef BINDLESS + let uv_transform = material_array[material_indices[slot].material].uv_transform; +#else // BINDLESS + let uv_transform = material.uv_transform; +#endif // BINDLESS + let uv = (uv_transform * vec3(in.uv, 1.0)).xy; + + // Multiply the base color by the `modulate_texture` and `modulate_color`. +#ifdef BINDLESS + // Notice how we fetch the texture, sampler, and plain extended material + // data from the appropriate arrays. + pbr_input.material.base_color *= textureSample( + bindless_textures_2d[example_extended_material_indices[slot].modulate_texture], + bindless_samplers_filtering[ + example_extended_material_indices[slot].modulate_texture_sampler + ], + uv + ) * example_extended_material[example_extended_material_indices[slot].material].modulate_color; +#else // BINDLESS + pbr_input.material.base_color *= textureSample(modulate_texture, modulate_sampler, uv) * + example_extended_material.modulate_color; +#endif // BINDLESS + + var out: FragmentOutput; + // Apply lighting. + out.color = apply_pbr_lighting(pbr_input); + // Apply in-shader post processing (fog, alpha-premultiply, and also + // tonemapping, debanding if the camera is non-HDR). Note this does not + // include fullscreen postprocessing effects like bloom. + out.color = main_pass_post_lighting_processing(pbr_input, out.color); + return out; +} diff --git a/assets/shaders/game_of_life.wgsl b/assets/shaders/game_of_life.wgsl index c5b94533e5..0eb5e32e6e 100644 --- a/assets/shaders/game_of_life.wgsl +++ b/assets/shaders/game_of_life.wgsl @@ -12,9 +12,9 @@ fn hash(value: u32) -> u32 { var state = value; state = state ^ 2747636419u; state = state * 2654435769u; - state = state ^ state >> 16u; + state = state ^ (state >> 16u); state = state * 2654435769u; - state = state ^ state >> 16u; + state = state ^ (state >> 16u); state = state * 2654435769u; return state; } @@ -27,7 +27,7 @@ fn randomFloat(value: u32) -> f32 { fn init(@builtin(global_invocation_id) invocation_id: vec3, @builtin(num_workgroups) num_workgroups: vec3) { let location = vec2(i32(invocation_id.x), i32(invocation_id.y)); - let randomNumber = randomFloat(invocation_id.y << 16u | invocation_id.x); + let randomNumber = randomFloat((invocation_id.y << 16u) | invocation_id.x); let alive = randomNumber > 0.9; let color = vec4(f32(alive)); diff --git a/assets/shaders/util.wesl b/assets/shaders/util.wesl index 52461fca48..ebbf023926 100644 --- a/assets/shaders/util.wesl +++ b/assets/shaders/util.wesl @@ -40,5 +40,5 @@ fn make_polka_dots(pos: vec2, time: f32) -> vec4 { is_dot = step(dist_from_center, 0.3 + wave_normalized * 0.2); } - return vec4(dot_color * is_dot, is_dot); -} \ No newline at end of file + return vec4(dot_color * is_dot, 1.0); +} diff --git a/assets/textures/food_kenney.png b/assets/textures/food_kenney.png new file mode 100644 index 0000000000..a5fe374eba Binary files /dev/null and b/assets/textures/food_kenney.png differ diff --git a/benches/Cargo.toml b/benches/Cargo.toml index f5faeec237..a299a6526c 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -24,7 +24,7 @@ bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } bevy_render = { path = "../crates/bevy_render" } bevy_tasks = { path = "../crates/bevy_tasks" } bevy_utils = { path = "../crates/bevy_utils" } -bevy_platform_support = { path = "../crates/bevy_platform_support", default-features = false, features = [ +bevy_platform = { path = "../crates/bevy_platform", default-features = false, features = [ "std", ] } @@ -32,6 +32,7 @@ bevy_platform_support = { path = "../crates/bevy_platform_support", default-feat glam = "0.29" rand = "0.8" rand_chacha = "0.3" +nonmax = { version = "0.5", default-features = false } # Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here # because the benches do not actually open any windows. diff --git a/benches/README.md b/benches/README.md index 2e91916e48..5f256f877b 100644 --- a/benches/README.md +++ b/benches/README.md @@ -25,10 +25,10 @@ cargo bench -p benches -- name_fragment cargo bench -p benches -- --list # Save a baseline to be compared against later. -cargo bench -p benches --save-baseline before +cargo bench -p benches -- --save-baseline before # Compare the current benchmarks against a baseline to find performance gains and regressions. -cargo bench -p benches --baseline before +cargo bench -p benches -- --baseline before ``` ## Criterion diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index 2908332ea5..4a4ddda9aa 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -51,8 +51,7 @@ fn add_archetypes(world: &mut World, count: u16) { pub fn no_archetypes(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("no_archetypes"); - for i in 0..=5 { - let system_count = i * 20; + for system_count in [0, 10, 100] { let (mut world, mut schedule) = setup(system_count); group.bench_with_input( BenchmarkId::new("system_count", system_count), @@ -69,7 +68,7 @@ pub fn no_archetypes(criterion: &mut Criterion) { pub fn added_archetypes(criterion: &mut Criterion) { const SYSTEM_COUNT: usize = 100; let mut group = criterion.benchmark_group("added_archetypes"); - for archetype_count in [100, 200, 500, 1000, 2000, 5000, 10000] { + for archetype_count in [100, 1_000, 10_000] { group.bench_with_input( BenchmarkId::new("archetype_count", archetype_count), &archetype_count, diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index e5e7639066..b131f1c9c3 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -155,7 +155,7 @@ fn add_archetypes(world: &mut World, count: u16) { fn empty_archetypes(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("empty_archetypes"); - for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { + for archetype_count in [10, 100, 1_000, 10_000] { let (mut world, mut schedule) = setup(true, |schedule| { schedule.add_systems(iter); }); @@ -186,7 +186,7 @@ fn empty_archetypes(criterion: &mut Criterion) { }, ); } - for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { + for archetype_count in [10, 100, 1_000, 10_000] { let (mut world, mut schedule) = setup(true, |schedule| { schedule.add_systems(for_each); }); @@ -217,7 +217,7 @@ fn empty_archetypes(criterion: &mut Criterion) { }, ); } - for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { + for archetype_count in [10, 100, 1_000, 10_000] { let (mut world, mut schedule) = setup(true, |schedule| { schedule.add_systems(par_for_each); }); diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 6d6561c02b..0eaae27ce4 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -155,7 +155,7 @@ fn bench_clone_hierarchy( for parent in current_hierarchy_level { for _ in 0..children { - let child_id = world.spawn((B::default(), ChildOf { parent })).id(); + let child_id = world.spawn((B::default(), ChildOf(parent))).id(); hierarchy_level.push(child_id); } } diff --git a/benches/benches/bevy_ecs/events/mod.rs b/benches/benches/bevy_ecs/events/mod.rs index 4367c45c3e..b87a138e06 100644 --- a/benches/benches/bevy_ecs/events/mod.rs +++ b/benches/benches/bevy_ecs/events/mod.rs @@ -9,19 +9,19 @@ fn send(c: &mut Criterion) { let mut group = c.benchmark_group("events_send"); group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_4_events_{}", count), |b| { let mut bench = send::Benchmark::<4>::new(count); b.iter(move || bench.run()); }); } - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_16_events_{}", count), |b| { let mut bench = send::Benchmark::<16>::new(count); b.iter(move || bench.run()); }); } - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_512_events_{}", count), |b| { let mut bench = send::Benchmark::<512>::new(count); b.iter(move || bench.run()); @@ -34,19 +34,19 @@ fn iter(c: &mut Criterion) { let mut group = c.benchmark_group("events_iter"); group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_4_events_{}", count), |b| { let mut bench = iter::Benchmark::<4>::new(count); b.iter(move || bench.run()); }); } - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_16_events_{}", count), |b| { let mut bench = iter::Benchmark::<4>::new(count); b.iter(move || bench.run()); }); } - for count in [100, 1000, 10000, 50000] { + for count in [100, 1_000, 10_000] { group.bench_function(format!("size_512_events_{}", count), |b| { let mut bench = iter::Benchmark::<512>::new(count); b.iter(move || bench.run()); diff --git a/benches/benches/bevy_ecs/iteration/heavy_compute.rs b/benches/benches/bevy_ecs/iteration/heavy_compute.rs index 3a3e350637..e057b20a43 100644 --- a/benches/benches/bevy_ecs/iteration/heavy_compute.rs +++ b/benches/benches/bevy_ecs/iteration/heavy_compute.rs @@ -45,7 +45,6 @@ pub fn heavy_compute(c: &mut Criterion) { let mut system = IntoSystem::into_system(sys); system.initialize(&mut world); - system.update_archetype_component_access(world.as_unsafe_world_cell()); b.iter(move || system.run((), &mut world)); }); diff --git a/benches/benches/bevy_ecs/iteration/iter_simple_system.rs b/benches/benches/bevy_ecs/iteration/iter_simple_system.rs index 2b6e828721..903ff08194 100644 --- a/benches/benches/bevy_ecs/iteration/iter_simple_system.rs +++ b/benches/benches/bevy_ecs/iteration/iter_simple_system.rs @@ -37,7 +37,6 @@ impl Benchmark { let mut system = IntoSystem::into_system(query_system); system.initialize(&mut world); - system.update_archetype_component_access(world.as_unsafe_world_cell()); Self(world, Box::new(system)) } diff --git a/benches/benches/bevy_ecs/scheduling/run_condition.rs b/benches/benches/bevy_ecs/scheduling/run_condition.rs index 0d6e4107c6..7b9cf418f4 100644 --- a/benches/benches/bevy_ecs/scheduling/run_condition.rs +++ b/benches/benches/bevy_ecs/scheduling/run_condition.rs @@ -17,15 +17,14 @@ pub fn run_condition_yes(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(3)); fn empty() {} - for amount in 0..21 { + for amount in [10, 100, 1_000] { let mut schedule = Schedule::default(); - schedule.add_systems(empty.run_if(yes)); - for _ in 0..amount { + for _ in 0..(amount / 5) { schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes)); } // run once to initialize systems schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); @@ -40,15 +39,14 @@ pub fn run_condition_no(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(3)); fn empty() {} - for amount in 0..21 { + for amount in [10, 100, 1_000] { let mut schedule = Schedule::default(); - schedule.add_systems(empty.run_if(no)); - for _ in 0..amount { + for _ in 0..(amount / 5) { schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no)); } // run once to initialize systems schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); @@ -70,17 +68,16 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) { fn yes_with_query(query: Single<&TestBool>) -> bool { query.0 } - for amount in 0..21 { + for amount in [10, 100, 1_000] { let mut schedule = Schedule::default(); - schedule.add_systems(empty.run_if(yes_with_query)); - for _ in 0..amount { + for _ in 0..(amount / 5) { schedule.add_systems( (empty, empty, empty, empty, empty).distributive_run_if(yes_with_query), ); } // run once to initialize systems schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); @@ -99,17 +96,16 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) { fn yes_with_resource(res: Res) -> bool { res.0 } - for amount in 0..21 { + for amount in [10, 100, 1_000] { let mut schedule = Schedule::default(); - schedule.add_systems(empty.run_if(yes_with_resource)); - for _ in 0..amount { + for _ in 0..(amount / 5) { schedule.add_systems( (empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource), ); } // run once to initialize systems schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); diff --git a/benches/benches/bevy_ecs/scheduling/running_systems.rs b/benches/benches/bevy_ecs/scheduling/running_systems.rs index 4a14553885..2fc1da1710 100644 --- a/benches/benches/bevy_ecs/scheduling/running_systems.rs +++ b/benches/benches/bevy_ecs/scheduling/running_systems.rs @@ -20,25 +20,25 @@ pub fn empty_systems(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(3)); fn empty() {} - for amount in 0..5 { + for amount in [0, 2, 4] { let mut schedule = Schedule::default(); for _ in 0..amount { schedule.add_systems(empty); } schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", amount), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); }); } - for amount in 1..21 { + for amount in [10, 100, 1_000] { let mut schedule = Schedule::default(); - for _ in 0..amount { + for _ in 0..(amount / 5) { schedule.add_systems((empty, empty, empty, empty, empty)); } schedule.run(&mut world); - group.bench_function(format!("{:03}_systems", 5 * amount), |bencher| { + group.bench_function(format!("{}_systems", amount), |bencher| { bencher.iter(|| { schedule.run(&mut world); }); @@ -67,23 +67,21 @@ pub fn busy_systems(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("busy_systems"); group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(3)); - for entity_bunches in 1..6 { + for entity_bunches in [1, 3, 5] { world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0)))); world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0)))); world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); - for system_amount in 0..5 { + for system_amount in [3, 9, 15] { let mut schedule = Schedule::default(); - schedule.add_systems((ab, cd, ce)); - for _ in 0..system_amount { + for _ in 0..(system_amount / 3) { schedule.add_systems((ab, cd, ce)); } schedule.run(&mut world); group.bench_function( format!( "{:02}x_entities_{:02}_systems", - entity_bunches, - 3 * system_amount + 3 + entity_bunches, system_amount ), |bencher| { bencher.iter(|| { @@ -119,22 +117,20 @@ pub fn contrived(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("contrived"); group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(3)); - for entity_bunches in 1..6 { + for entity_bunches in [1, 3, 5] { world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0)))); world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0)))); - for system_amount in 0..5 { + for system_amount in [3, 9, 15] { let mut schedule = Schedule::default(); - schedule.add_systems((s_0, s_1, s_2)); - for _ in 0..system_amount { + for _ in 0..(system_amount / 3) { schedule.add_systems((s_0, s_1, s_2)); } schedule.run(&mut world); group.bench_function( format!( "{:02}x_entities_{:02}_systems", - entity_bunches, - 3 * system_amount + 3 + entity_bunches, system_amount ), |bencher| { bencher.iter(|| { diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 9844461d39..d7d1243f7f 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -137,6 +137,7 @@ pub fn empty_schedule_run(criterion: &mut Criterion) { }); let mut schedule = Schedule::default(); + #[expect(deprecated, reason = "We still need to test/bench this.")] schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::Simple); group.bench_function("Simple", |bencher| { bencher.iter(|| schedule.run(app.world_mut())); diff --git a/benches/benches/bevy_ecs/world/commands.rs b/benches/benches/bevy_ecs/world/commands.rs index 8ad87862eb..bedfb8e5af 100644 --- a/benches/benches/bevy_ecs/world/commands.rs +++ b/benches/benches/bevy_ecs/world/commands.rs @@ -36,7 +36,7 @@ pub fn spawn_commands(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for entity_count in (1..5).map(|i| i * 2 * 1000) { + for entity_count in [100, 1_000, 10_000] { group.bench_function(format!("{}_entities", entity_count), |bencher| { let mut world = World::default(); let mut command_queue = CommandQueue::default(); @@ -62,6 +62,31 @@ pub fn spawn_commands(criterion: &mut Criterion) { group.finish(); } +pub fn nonempty_spawn_commands(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("nonempty_spawn_commands"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(4)); + + for entity_count in [100, 1_000, 10_000] { + group.bench_function(format!("{}_entities", entity_count), |bencher| { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + + bencher.iter(|| { + let mut commands = Commands::new(&mut command_queue, &world); + for i in 0..entity_count { + if black_box(i % 2 == 0) { + commands.spawn(A); + } + } + command_queue.apply(&mut world); + }); + }); + } + + group.finish(); +} + #[derive(Default, Component)] struct Matrix([[f32; 4]; 4]); @@ -92,28 +117,6 @@ pub fn insert_commands(criterion: &mut Criterion) { command_queue.apply(&mut world); }); }); - group.bench_function("insert_or_spawn_batch", |bencher| { - let mut world = World::default(); - let mut command_queue = CommandQueue::default(); - let mut entities = Vec::new(); - for _ in 0..entity_count { - entities.push(world.spawn_empty().id()); - } - - bencher.iter(|| { - let mut commands = Commands::new(&mut command_queue, &world); - let mut values = Vec::with_capacity(entity_count); - for entity in &entities { - values.push((*entity, (Matrix::default(), Vec3::default()))); - } - #[expect( - deprecated, - reason = "This needs to be supported for now, and therefore still needs the benchmark." - )] - commands.insert_or_spawn_batch(values); - command_queue.apply(&mut world); - }); - }); group.bench_function("insert_batch", |bencher| { let mut world = World::default(); let mut command_queue = CommandQueue::default(); @@ -158,7 +161,7 @@ pub fn fake_commands(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for command_count in (1..5).map(|i| i * 2 * 1000) { + for command_count in [100, 1_000, 10_000] { group.bench_function(format!("{}_commands", command_count), |bencher| { let mut world = World::default(); let mut command_queue = CommandQueue::default(); @@ -203,7 +206,7 @@ pub fn sized_commands_impl(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for command_count in (1..5).map(|i| i * 2 * 1000) { + for command_count in [100, 1_000, 10_000] { group.bench_function(format!("{}_commands", command_count), |bencher| { let mut world = World::default(); let mut command_queue = CommandQueue::default(); diff --git a/benches/benches/bevy_ecs/world/despawn.rs b/benches/benches/bevy_ecs/world/despawn.rs index ace88e744a..cd693fc15c 100644 --- a/benches/benches/bevy_ecs/world/despawn.rs +++ b/benches/benches/bevy_ecs/world/despawn.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use criterion::Criterion; +use criterion::{BatchSize, Criterion}; use glam::*; #[derive(Component)] @@ -12,19 +12,24 @@ pub fn world_despawn(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for entity_count in (0..5).map(|i| 10_u32.pow(i)) { - let mut world = World::default(); - for _ in 0..entity_count { - world.spawn((A(Mat4::default()), B(Vec4::default()))); - } - - let ents = world.iter_entities().map(|e| e.id()).collect::>(); + for entity_count in [1, 100, 10_000] { group.bench_function(format!("{}_entities", entity_count), |bencher| { - bencher.iter(|| { - ents.iter().for_each(|e| { - world.despawn(*e); - }); - }); + bencher.iter_batched_ref( + || { + let mut world = World::default(); + for _ in 0..entity_count { + world.spawn((A(Mat4::default()), B(Vec4::default()))); + } + let ents = world.iter_entities().map(|e| e.id()).collect::>(); + (world, ents) + }, + |(world, ents)| { + ents.iter().for_each(|e| { + world.despawn(*e); + }); + }, + BatchSize::SmallInput, + ); }); } diff --git a/benches/benches/bevy_ecs/world/despawn_recursive.rs b/benches/benches/bevy_ecs/world/despawn_recursive.rs index dd1ca4325b..78c644174b 100644 --- a/benches/benches/bevy_ecs/world/despawn_recursive.rs +++ b/benches/benches/bevy_ecs/world/despawn_recursive.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use criterion::Criterion; +use criterion::{BatchSize, Criterion}; use glam::*; #[derive(Component)] @@ -12,23 +12,31 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for entity_count in (0..5).map(|i| 10_u32.pow(i)) { - let mut world = World::default(); - for _ in 0..entity_count { - world - .spawn((A(Mat4::default()), B(Vec4::default()))) - .with_children(|parent| { - parent.spawn((A(Mat4::default()), B(Vec4::default()))); - }); - } - - let ents = world.iter_entities().map(|e| e.id()).collect::>(); + for entity_count in [1, 100, 10_000] { group.bench_function(format!("{}_entities", entity_count), |bencher| { - bencher.iter(|| { - ents.iter().for_each(|e| { - world.entity_mut(*e).despawn(); - }); - }); + bencher.iter_batched_ref( + || { + let mut world = World::default(); + let parent_ents = (0..entity_count) + .map(|_| { + world + .spawn((A(Mat4::default()), B(Vec4::default()))) + .with_children(|parent| { + parent.spawn((A(Mat4::default()), B(Vec4::default()))); + }) + .id() + }) + .collect::>(); + + (world, parent_ents) + }, + |(world, parent_ents)| { + parent_ents.iter().for_each(|e| { + world.despawn(*e); + }); + }, + BatchSize::SmallInput, + ); }); } diff --git a/benches/benches/bevy_ecs/world/entity_hash.rs b/benches/benches/bevy_ecs/world/entity_hash.rs index af7987bb02..2f92a715b4 100644 --- a/benches/benches/bevy_ecs/world/entity_hash.rs +++ b/benches/benches/bevy_ecs/world/entity_hash.rs @@ -1,4 +1,4 @@ -use bevy_ecs::entity::{hash_set::EntityHashSet, Entity}; +use bevy_ecs::entity::{Entity, EntityGeneration, EntityHashSet}; use criterion::{BenchmarkId, Criterion, Throughput}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; @@ -17,10 +17,14 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity { let generation = 1.0 + -(1.0 - x).log2() * 2.0; // this is not reliable, but we're internal so a hack is ok - let bits = ((generation as u64) << 32) | (id as u64); + let id = id as u64 + 1; + let bits = ((generation as u64) << 32) | id; let e = Entity::from_bits(bits); - assert_eq!(e.index(), id as u32); - assert_eq!(e.generation(), generation as u32); + assert_eq!(e.index(), !(id as u32)); + assert_eq!( + e.generation(), + EntityGeneration::FIRST.after_versions(generation as u32) + ); e } diff --git a/benches/benches/bevy_ecs/world/mod.rs b/benches/benches/bevy_ecs/world/mod.rs index e35dc999c2..7158f2f033 100644 --- a/benches/benches/bevy_ecs/world/mod.rs +++ b/benches/benches/bevy_ecs/world/mod.rs @@ -17,6 +17,7 @@ criterion_group!( benches, empty_commands, spawn_commands, + nonempty_spawn_commands, insert_commands, fake_commands, zero_sized_commands, diff --git a/benches/benches/bevy_ecs/world/spawn.rs b/benches/benches/bevy_ecs/world/spawn.rs index 0777a20cb9..502d10ceb3 100644 --- a/benches/benches/bevy_ecs/world/spawn.rs +++ b/benches/benches/bevy_ecs/world/spawn.rs @@ -12,7 +12,7 @@ pub fn world_spawn(criterion: &mut Criterion) { group.warm_up_time(core::time::Duration::from_millis(500)); group.measurement_time(core::time::Duration::from_secs(4)); - for entity_count in (0..5).map(|i| 10_u32.pow(i)) { + for entity_count in [1, 100, 10_000] { group.bench_function(format!("{}_entities", entity_count), |bencher| { let mut world = World::default(); bencher.iter(|| { diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 283b984186..e6e2a0bb90 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -1,9 +1,10 @@ use core::hint::black_box; +use nonmax::NonMaxU32; use bevy_ecs::{ bundle::{Bundle, NoBundleEffect}, component::Component, - entity::Entity, + entity::{Entity, EntityRow}, system::{Query, SystemState}, world::World, }; @@ -53,7 +54,9 @@ pub fn world_entity(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); black_box(world.entity(entity)); } }); @@ -74,7 +77,9 @@ pub fn world_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(world.get::(entity).is_some()); } }); @@ -84,7 +89,9 @@ pub fn world_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(world.get::(entity).is_some()); } }); @@ -106,7 +113,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -131,7 +140,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -142,7 +153,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -169,7 +182,10 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + // SAFETY: Range is exclusive. + let entity = Entity::from_raw(EntityRow::new(unsafe { + NonMaxU32::new_unchecked(i) + })); assert!(query.get(&world, entity).is_ok()); } }); diff --git a/benches/benches/bevy_picking/ray_mesh_intersection.rs b/benches/benches/bevy_picking/ray_mesh_intersection.rs index ee81f1ac7f..871a6d1062 100644 --- a/benches/benches/bevy_picking/ray_mesh_intersection.rs +++ b/benches/benches/bevy_picking/ray_mesh_intersection.rs @@ -37,7 +37,7 @@ fn create_mesh(vertices_per_side: u32) -> SimpleMesh { for p in 0..vertices_per_side.pow(2) { let (x, z) = p_to_xz_norm(p, vertices_per_side); - // Push a new vertice to the mesh. We translate all vertices so the final square is + // Push a new vertex to the mesh. We translate all vertices so the final square is // centered at (0, 0), instead of (0.5, 0.5). positions.push([x - 0.5, 0.0, z - 0.5]); diff --git a/benches/benches/bevy_reflect/map.rs b/benches/benches/bevy_reflect/map.rs index e35af1c992..1eab01a587 100644 --- a/benches/benches/bevy_reflect/map.rs +++ b/benches/benches/bevy_reflect/map.rs @@ -1,7 +1,7 @@ use core::{fmt::Write, hint::black_box, iter, time::Duration}; use benches::bench; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{DynamicMap, Map}; use criterion::{ criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup, BenchmarkId, diff --git a/benches/benches/bevy_render/compute_normals.rs b/benches/benches/bevy_render/compute_normals.rs new file mode 100644 index 0000000000..41bda05de9 --- /dev/null +++ b/benches/benches/bevy_render/compute_normals.rs @@ -0,0 +1,96 @@ +use core::hint::black_box; + +use criterion::{criterion_group, Criterion}; +use rand::random; +use std::time::{Duration, Instant}; + +use bevy_render::{ + mesh::{Indices, Mesh, PrimitiveTopology}, + render_asset::RenderAssetUsages, +}; + +const GRID_SIZE: usize = 256; + +fn compute_normals(c: &mut Criterion) { + let indices = Indices::U32( + (0..GRID_SIZE - 1) + .flat_map(|i| std::iter::repeat(i).zip(0..GRID_SIZE - 1)) + .flat_map(|(i, j)| { + let tl = ((GRID_SIZE * j) + i) as u32; + let tr = tl + 1; + let bl = ((GRID_SIZE * (j + 1)) + i) as u32; + let br = bl + 1; + [tl, bl, tr, tr, bl, br] + }) + .collect(), + ); + + let new_mesh = || { + let positions = (0..GRID_SIZE) + .flat_map(|i| std::iter::repeat(i).zip(0..GRID_SIZE)) + .map(|(i, j)| [i as f32, j as f32, random::()]) + .collect::>(); + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::MAIN_WORLD, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_indices(indices.clone()) + }; + + c.bench_function("smooth_normals", |b| { + b.iter_custom(|iters| { + let mut total = Duration::default(); + for _ in 0..iters { + let mut mesh = new_mesh(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + let start = Instant::now(); + mesh.compute_smooth_normals(); + let end = Instant::now(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + total += end.duration_since(start); + } + total + }); + }); + + c.bench_function("face_weighted_normals", |b| { + b.iter_custom(|iters| { + let mut total = Duration::default(); + for _ in 0..iters { + let mut mesh = new_mesh(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + let start = Instant::now(); + mesh.compute_smooth_normals(); + let end = Instant::now(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + total += end.duration_since(start); + } + total + }); + }); + + let new_mesh = || { + new_mesh() + .with_duplicated_vertices() + .with_computed_flat_normals() + }; + + c.bench_function("flat_normals", |b| { + b.iter_custom(|iters| { + let mut total = Duration::default(); + for _ in 0..iters { + let mut mesh = new_mesh(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + let start = Instant::now(); + mesh.compute_flat_normals(); + let end = Instant::now(); + black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); + total += end.duration_since(start); + } + total + }); + }); +} + +criterion_group!(benches, compute_normals); diff --git a/benches/benches/bevy_render/main.rs b/benches/benches/bevy_render/main.rs index 7a369bc905..e335670222 100644 --- a/benches/benches/bevy_render/main.rs +++ b/benches/benches/bevy_render/main.rs @@ -1,6 +1,11 @@ use criterion::criterion_main; +mod compute_normals; mod render_layers; mod torus; -criterion_main!(render_layers::benches, torus::benches); +criterion_main!( + render_layers::benches, + compute_normals::benches, + torus::benches +); diff --git a/clippy.toml b/clippy.toml index 2c98e8ed02..372ffbaf0b 100644 --- a/clippy.toml +++ b/clippy.toml @@ -41,7 +41,6 @@ disallowed-methods = [ { path = "f32::asinh", reason = "use bevy_math::ops::asinh instead for libm determinism" }, { path = "f32::acosh", reason = "use bevy_math::ops::acosh instead for libm determinism" }, { path = "f32::atanh", reason = "use bevy_math::ops::atanh instead for libm determinism" }, - { path = "criterion::black_box", reason = "use core::hint::black_box instead" }, ] # Require `bevy_ecs::children!` to use `[]` braces, instead of `()` or `{}`. diff --git a/crates/bevy_a11y/Cargo.toml b/crates/bevy_a11y/Cargo.toml index 3a6ece0652..5ffab33d63 100644 --- a/crates/bevy_a11y/Cargo.toml +++ b/crates/bevy_a11y/Cargo.toml @@ -18,28 +18,17 @@ bevy_reflect = [ "dep:bevy_reflect", "bevy_app/bevy_reflect", "bevy_ecs/bevy_reflect", - "bevy_input_focus/bevy_reflect", ] ## Adds serialization support through `serde`. -serialize = [ - "dep:serde", - "bevy_ecs/serialize", - "bevy_input_focus/serialize", - "accesskit/serde", -] +serialize = ["dep:serde", "bevy_ecs/serialize", "accesskit/serde"] # Platform Compatibility ## Allows access to the `std` crate. Enabling this feature will prevent compilation ## on `no_std` targets, but provides access to certain additional features on ## supported platforms. -std = [ - "bevy_app/std", - "bevy_ecs/std", - "bevy_reflect/std", - "bevy_input_focus/std", -] +std = ["bevy_app/std", "bevy_ecs/std", "bevy_reflect/std"] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. @@ -47,22 +36,17 @@ critical-section = [ "bevy_app/critical-section", "bevy_ecs/critical-section", "bevy_reflect?/critical-section", - "bevy_input_focus/critical-section", ] -## Uses the `libm` maths library instead of the one provided in `std` and `core`. -libm = ["bevy_input_focus/libm"] - [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } -bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev", default-features = false } # other -accesskit = { version = "0.17", default-features = false } +accesskit = { version = "0.19", default-features = false } serde = { version = "1", default-features = false, features = [ "alloc", ], optional = true } diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs index 910ec3ca35..94468c148c 100644 --- a/crates/bevy_a11y/src/lib.rs +++ b/crates/bevy_a11y/src/lib.rs @@ -137,11 +137,15 @@ impl From for AccessibilityNode { all(feature = "bevy_reflect", feature = "serialize"), reflect(Serialize, Deserialize, Clone) )] -pub enum AccessibilitySystem { +pub enum AccessibilitySystems { /// Update the accessibility tree Update, } +/// Deprecated alias for [`AccessibilitySystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `AccessibilitySystems`.")] +pub type AccessibilitySystem = AccessibilitySystems; + /// Plugin managing non-GUI aspects of integrating with accessibility APIs. #[derive(Default)] pub struct AccessibilityPlugin; diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 72e42a6878..11e819806c 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -16,6 +16,7 @@ bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } +bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "petgraph", ] } @@ -24,7 +25,7 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } diff --git a/crates/bevy_animation/src/animation_curves.rs b/crates/bevy_animation/src/animation_curves.rs index 4c944cdf6d..45fa393e05 100644 --- a/crates/bevy_animation/src/animation_curves.rs +++ b/crates/bevy_animation/src/animation_curves.rs @@ -100,43 +100,48 @@ use bevy_math::curve::{ iterable::IterableCurve, Curve, Interval, }; -use bevy_platform_support::hash::Hashed; +use bevy_mesh::morph::MorphWeights; +use bevy_platform::hash::Hashed; use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed}; -use bevy_render::mesh::morph::MorphWeights; use downcast_rs::{impl_downcast, Downcast}; -/// A value on a component that Bevy can animate. +/// A trait for exposing a value in an entity so that it can be animated. /// -/// You can implement this trait on a unit struct in order to support animating -/// custom components other than transforms and morph weights. Use that type in -/// conjunction with [`AnimatableCurve`] (and perhaps [`AnimatableKeyframeCurve`] -/// to define the animation itself). -/// For example, in order to animate field of view, you might use: +/// `AnimatableProperty` allows any value contained in an entity to be animated +/// as long as it can be obtained by mutable reference. This makes it more +/// flexible than [`animated_field`]. +/// +/// [`animated_field`]: crate::animated_field +/// +/// Here, `AnimatableProperty` is used to animate a value inside an `Option`, +/// returning an error if the option is `None`. /// /// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId}; -/// # use bevy_reflect::Reflect; +/// # use bevy_ecs::component::Component; /// # use std::any::TypeId; -/// # use bevy_render::camera::{Projection, PerspectiveProjection}; -/// #[derive(Reflect)] -/// struct FieldOfViewProperty; +/// #[derive(Component)] +/// struct ExampleComponent { +/// power_level: Option +/// } /// -/// impl AnimatableProperty for FieldOfViewProperty { +/// #[derive(Clone)] +/// struct PowerLevelProperty; +/// +/// impl AnimatableProperty for PowerLevelProperty { /// type Property = f32; -/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> { +/// fn get_mut<'a>( +/// &self, +/// entity: &'a mut AnimationEntityMut +/// ) -> Result<&'a mut Self::Property, AnimationEvaluationError> { /// let component = entity -/// .get_mut::() -/// .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::< -/// Projection, -/// >( -/// )))? +/// .get_mut::() +/// .ok_or(AnimationEvaluationError::ComponentNotPresent( +/// TypeId::of::() +/// ))? /// .into_inner(); -/// match component { -/// Projection::Perspective(perspective) => Ok(&mut perspective.fov), -/// _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::< -/// PerspectiveProjection, -/// >( -/// ))), -/// } +/// component.power_level.as_mut().ok_or(AnimationEvaluationError::PropertyNotPresent( +/// TypeId::of::>() +/// )) /// } /// /// fn evaluator_id(&self) -> EvaluatorId { @@ -144,58 +149,44 @@ use downcast_rs::{impl_downcast, Downcast}; /// } /// } /// -/// You can then create an [`AnimationClip`] to animate this property like so: /// -/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId}; +/// You can then create an [`AnimatableCurve`] to animate this property like so: +/// +/// # use bevy_animation::{VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId}; /// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve}; -/// # use bevy_ecs::name::Name; -/// # use bevy_reflect::Reflect; -/// # use bevy_render::camera::{Projection, PerspectiveProjection}; +/// # use bevy_ecs::{name::Name, component::Component}; /// # use std::any::TypeId; -/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test")); -/// # #[derive(Reflect, Clone)] -/// # struct FieldOfViewProperty; -/// # impl AnimatableProperty for FieldOfViewProperty { -/// # type Property = f32; -/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> { -/// # let component = entity -/// # .get_mut::() -/// # .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::< -/// # Projection, -/// # >( -/// # )))? -/// # .into_inner(); -/// # match component { -/// # Projection::Perspective(perspective) => Ok(&mut perspective.fov), -/// # _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::< -/// # PerspectiveProjection, -/// # >( -/// # ))), -/// # } -/// # } -/// # fn evaluator_id(&self) -> EvaluatorId { -/// # EvaluatorId::Type(TypeId::of::()) -/// # } +/// # #[derive(Component)] +/// # struct ExampleComponent { power_level: Option } +/// # #[derive(Clone)] +/// # struct PowerLevelProperty; +/// # impl AnimatableProperty for PowerLevelProperty { +/// # type Property = f32; +/// # fn get_mut<'a>( +/// # &self, +/// # entity: &'a mut AnimationEntityMut +/// # ) -> Result<&'a mut Self::Property, AnimationEvaluationError> { +/// # let component = entity +/// # .get_mut::() +/// # .ok_or(AnimationEvaluationError::ComponentNotPresent( +/// # TypeId::of::() +/// # ))? +/// # .into_inner(); +/// # component.power_level.as_mut().ok_or(AnimationEvaluationError::PropertyNotPresent( +/// # TypeId::of::>() +/// # )) +/// # } +/// # fn evaluator_id(&self) -> EvaluatorId { +/// # EvaluatorId::Type(TypeId::of::()) +/// # } /// # } -/// let mut animation_clip = AnimationClip::default(); -/// animation_clip.add_curve_to_target( -/// animation_target_id, -/// AnimatableCurve::new( -/// FieldOfViewProperty, -/// AnimatableKeyframeCurve::new([ -/// (0.0, core::f32::consts::PI / 4.0), -/// (1.0, core::f32::consts::PI / 3.0), -/// ]).expect("Failed to create font size curve") -/// ) +/// AnimatableCurve::new( +/// PowerLevelProperty, +/// AnimatableKeyframeCurve::new([ +/// (0.0, 0.0), +/// (1.0, 9001.0), +/// ]).expect("Failed to create power level curve") /// ); -/// -/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value -/// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The -/// invocation of [`AnimatableCurve::new`] with `FieldOfViewProperty` indicates that the `f32` -/// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as -/// configured above). -/// -/// [`AnimationClip`]: crate::AnimationClip pub trait AnimatableProperty: Send + Sync + 'static { /// The animated property type. type Property: Animatable; diff --git a/crates/bevy_animation/src/gltf_curves.rs b/crates/bevy_animation/src/gltf_curves.rs index f69ce7385c..688011a32c 100644 --- a/crates/bevy_animation/src/gltf_curves.rs +++ b/crates/bevy_animation/src/gltf_curves.rs @@ -373,7 +373,7 @@ impl WideCubicKeyframeCurve { /// recommended to use its implementation of the [`IterableCurve`] trait, which allows iterating /// directly over information derived from the curve without allocating. /// -/// [`MorphWeights`]: bevy_render::prelude::MorphWeights +/// [`MorphWeights`]: bevy_mesh::morph::MorphWeights #[derive(Debug, Clone, Reflect)] #[reflect(Clone)] pub enum WeightsCurve { diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index 15fd96c63b..aa6d252fee 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -17,7 +17,7 @@ use bevy_ecs::{ resource::Resource, system::{Res, ResMut}, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectSerialize}; use derive_more::derive::From; use petgraph::{ diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index b3ecc085c6..21ea15f96f 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -31,14 +31,14 @@ use crate::{ prelude::EvaluatorId, }; -use bevy_app::{Animation, App, Plugin, PostUpdate}; -use bevy_asset::{Asset, AssetApp, AssetEvents, Assets}; +use bevy_app::{AnimationSystems, App, Plugin, PostUpdate}; +use bevy_asset::{Asset, AssetApp, AssetEventSystems, Assets}; use bevy_ecs::{prelude::*, world::EntityMutExcept}; use bevy_math::FloatOrd; -use bevy_platform_support::{collections::HashMap, hash::NoOpHash}; +use bevy_platform::{collections::HashMap, hash::NoOpHash}; use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; use bevy_time::Time; -use bevy_transform::TransformSystem; +use bevy_transform::TransformSystems; use bevy_utils::{PreHashMap, PreHashMapExt, TypeIdMap}; use petgraph::graph::NodeIndex; use serde::{Deserialize, Serialize}; @@ -685,7 +685,6 @@ impl ActiveAnimation { #[reflect(Component, Default, Clone)] pub struct AnimationPlayer { active_animations: HashMap, - blend_weights: HashMap, } // This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`. @@ -693,13 +692,11 @@ impl Clone for AnimationPlayer { fn clone(&self) -> Self { Self { active_animations: self.active_animations.clone(), - blend_weights: self.blend_weights.clone(), } } fn clone_from(&mut self, source: &Self) { self.active_animations.clone_from(&source.active_animations); - self.blend_weights.clone_from(&source.blend_weights); } } @@ -758,10 +755,10 @@ impl AnimationCurveEvaluators { .component_property_curve_evaluators .get_or_insert_with(component_property, func), EvaluatorId::Type(type_id) => match self.type_id_curve_evaluators.entry(type_id) { - bevy_platform_support::collections::hash_map::Entry::Occupied(occupied_entry) => { + bevy_platform::collections::hash_map::Entry::Occupied(occupied_entry) => { &mut **occupied_entry.into_mut() } - bevy_platform_support::collections::hash_map::Entry::Vacant(vacant_entry) => { + bevy_platform::collections::hash_map::Entry::Vacant(vacant_entry) => { &mut **vacant_entry.insert(func()) } }, @@ -1247,7 +1244,7 @@ impl Plugin for AnimationPlugin { .add_systems( PostUpdate, ( - graph::thread_animation_graphs.before(AssetEvents), + graph::thread_animation_graphs.before(AssetEventSystems), advance_transitions, advance_animations, // TODO: `animate_targets` can animate anything, so @@ -1263,8 +1260,8 @@ impl Plugin for AnimationPlugin { expire_completed_transitions, ) .chain() - .in_set(Animation) - .before(TransformSystem::TransformPropagate), + .in_set(AnimationSystems) + .before(TransformSystems::Propagate), ); } } @@ -1533,6 +1530,8 @@ impl<'a> Iterator for TriggeredEventsIter<'a> { #[cfg(test)] mod tests { + use bevy_reflect::{DynamicMap, Map}; + use super::*; #[derive(Event, Reflect, Clone)] @@ -1664,4 +1663,13 @@ mod tests { active_animation.update(clip.duration, clip.duration); // 0.3 : 0.0 assert_triggered_events_with(&active_animation, &clip, [0.3, 0.2]); } + + #[test] + fn test_animation_node_index_as_key_of_dynamic_map() { + let mut map = DynamicMap::default(); + map.insert_boxed( + Box::new(AnimationNodeIndex::new(0)), + Box::new(ActiveAnimation::default()), + ); + } } diff --git a/crates/bevy_animation/src/transition.rs b/crates/bevy_animation/src/transition.rs index b287c1f2a9..4948559704 100644 --- a/crates/bevy_animation/src/transition.rs +++ b/crates/bevy_animation/src/transition.rs @@ -118,8 +118,9 @@ pub fn advance_transitions( // is divided between all the other layers, eventually culminating in the // currently-playing animation receiving whatever's left. This results in a // nicely normalized weight. - let mut remaining_weight = 1.0; for (mut animation_transitions, mut player) in query.iter_mut() { + let mut remaining_weight = 1.0; + for transition in &mut animation_transitions.transitions.iter_mut().rev() { // Decrease weight. transition.current_weight = (transition.current_weight diff --git a/crates/bevy_anti_aliasing/Cargo.toml b/crates/bevy_anti_aliasing/Cargo.toml new file mode 100644 index 0000000000..5a8e48ecb5 --- /dev/null +++ b/crates/bevy_anti_aliasing/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "bevy_anti_aliasing" +version = "0.16.0-dev" +edition = "2024" +description = "Provides various anti aliasing implementations for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[features] +trace = [] +webgl = [] +webgpu = [] +smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"] + +[dependencies] +# bevy +bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } +bevy_render = { path = "../bevy_render", version = "0.16.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } +bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } +bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" } + +# other +tracing = { version = "0.1", default-features = false, features = ["std"] } + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_platform_support/LICENSE-APACHE b/crates/bevy_anti_aliasing/LICENSE-APACHE similarity index 100% rename from crates/bevy_platform_support/LICENSE-APACHE rename to crates/bevy_anti_aliasing/LICENSE-APACHE diff --git a/crates/bevy_platform_support/LICENSE-MIT b/crates/bevy_anti_aliasing/LICENSE-MIT similarity index 100% rename from crates/bevy_platform_support/LICENSE-MIT rename to crates/bevy_anti_aliasing/LICENSE-MIT diff --git a/crates/bevy_anti_aliasing/README.md b/crates/bevy_anti_aliasing/README.md new file mode 100644 index 0000000000..ba0123c31b --- /dev/null +++ b/crates/bevy_anti_aliasing/README.md @@ -0,0 +1,7 @@ +# Bevy Anti Aliasing + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy_core_pipeline.svg)](https://crates.io/crates/bevy_core_pipeline) +[![Downloads](https://img.shields.io/crates/d/bevy_core_pipeline.svg)](https://crates.io/crates/bevy_core_pipeline) +[![Docs](https://docs.rs/bevy_core_pipeline/badge.svg)](https://docs.rs/bevy_core_pipeline/latest/bevy_core_pipeline/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs similarity index 94% rename from crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs rename to crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs index ab8f38cb6e..0b4a99fb59 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/mod.rs @@ -1,10 +1,10 @@ -use crate::{ +use bevy_app::prelude::*; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; +use bevy_core_pipeline::{ core_2d::graph::{Core2d, Node2d}, core_3d::graph::{Core3d, Node3d}, fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; -use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -18,7 +18,7 @@ use bevy_render::{ }, renderer::RenderDevice, view::{ExtractedView, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; mod node; @@ -95,20 +95,12 @@ impl ExtractComponent for ContrastAdaptiveSharpening { } } -const CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE: Handle = - weak_handle!("ef83f0a5-51df-4b51-9ab7-b5fd1ae5a397"); - /// Adds Support for Contrast Adaptive Sharpening (CAS). pub struct CasPlugin; impl Plugin for CasPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE, - "robust_contrast_adaptive_sharpening.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl"); app.register_type::(); app.add_plugins(( @@ -121,7 +113,7 @@ impl Plugin for CasPlugin { }; render_app .init_resource::>() - .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare)); + .add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare)); { render_app @@ -171,6 +163,7 @@ impl Plugin for CasPlugin { pub struct CasPipeline { texture_bind_group: BindGroupLayout, sampler: Sampler, + shader: Handle, } impl FromWorld for CasPipeline { @@ -194,6 +187,7 @@ impl FromWorld for CasPipeline { CasPipeline { texture_bind_group, sampler, + shader: load_embedded_asset!(render_world, "robust_contrast_adaptive_sharpening.wgsl"), } } } @@ -217,7 +211,7 @@ impl SpecializedRenderPipeline for CasPipeline { layout: vec![self.texture_bind_group.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs similarity index 100% rename from crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs rename to crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/node.rs diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl b/crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl similarity index 100% rename from crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl rename to crates/bevy_anti_aliasing/src/contrast_adaptive_sharpening/robust_contrast_adaptive_sharpening.wgsl diff --git a/crates/bevy_anti_aliasing/src/experimental/mod.rs b/crates/bevy_anti_aliasing/src/experimental/mod.rs new file mode 100644 index 0000000000..a8dc522c56 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/experimental/mod.rs @@ -0,0 +1,9 @@ +//! Experimental rendering features. +//! +//! Experimental features are features with known problems, missing features, +//! compatibility issues, low performance, and/or future breaking changes, but +//! are included nonetheless for testing purposes. + +pub mod taa { + pub use crate::taa::{TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing}; +} diff --git a/crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl b/crates/bevy_anti_aliasing/src/fxaa/fxaa.wgsl similarity index 100% rename from crates/bevy_core_pipeline/src/fxaa/fxaa.wgsl rename to crates/bevy_anti_aliasing/src/fxaa/fxaa.wgsl diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_anti_aliasing/src/fxaa/mod.rs similarity index 93% rename from crates/bevy_core_pipeline/src/fxaa/mod.rs rename to crates/bevy_anti_aliasing/src/fxaa/mod.rs index b69df0bf7b..6b914c4e86 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/fxaa/mod.rs @@ -1,10 +1,10 @@ -use crate::{ +use bevy_app::prelude::*; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; +use bevy_core_pipeline::{ core_2d::graph::{Core2d, Node2d}, core_3d::graph::{Core3d, Node3d}, fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; -use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::prelude::*; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -18,7 +18,7 @@ use bevy_render::{ }, renderer::RenderDevice, view::{ExtractedView, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_utils::default; @@ -80,13 +80,11 @@ impl Default for Fxaa { } } -const FXAA_SHADER_HANDLE: Handle = weak_handle!("fc58c0a8-01c0-46e9-94cc-83a794bae7b0"); - /// Adds support for Fast Approximate Anti-Aliasing (FXAA) pub struct FxaaPlugin; impl Plugin for FxaaPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl); + embedded_asset!(app, "fxaa.wgsl"); app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); @@ -96,7 +94,10 @@ impl Plugin for FxaaPlugin { }; render_app .init_resource::>() - .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)) + .add_systems( + Render, + prepare_fxaa_pipelines.in_set(RenderSystems::Prepare), + ) .add_render_graph_node::>(Core3d, Node3d::Fxaa) .add_render_graph_edges( Core3d, @@ -129,6 +130,7 @@ impl Plugin for FxaaPlugin { pub struct FxaaPipeline { texture_bind_group: BindGroupLayout, sampler: Sampler, + shader: Handle, } impl FromWorld for FxaaPipeline { @@ -155,6 +157,7 @@ impl FromWorld for FxaaPipeline { FxaaPipeline { texture_bind_group, sampler, + shader: load_embedded_asset!(render_world, "fxaa.wgsl"), } } } @@ -180,7 +183,7 @@ impl SpecializedRenderPipeline for FxaaPipeline { layout: vec![self.texture_bind_group.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: FXAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![ format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(), format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(), diff --git a/crates/bevy_core_pipeline/src/fxaa/node.rs b/crates/bevy_anti_aliasing/src/fxaa/node.rs similarity index 100% rename from crates/bevy_core_pipeline/src/fxaa/node.rs rename to crates/bevy_anti_aliasing/src/fxaa/node.rs diff --git a/crates/bevy_anti_aliasing/src/lib.rs b/crates/bevy_anti_aliasing/src/lib.rs new file mode 100644 index 0000000000..be09a2e5b2 --- /dev/null +++ b/crates/bevy_anti_aliasing/src/lib.rs @@ -0,0 +1,27 @@ +#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc( + html_logo_url = "https://bevyengine.org/assets/icon.png", + html_favicon_url = "https://bevyengine.org/assets/icon.png" +)] + +use bevy_app::Plugin; +use contrast_adaptive_sharpening::CasPlugin; +use fxaa::FxaaPlugin; +use smaa::SmaaPlugin; + +pub mod contrast_adaptive_sharpening; +pub mod experimental; +pub mod fxaa; +pub mod smaa; + +mod taa; + +#[derive(Default)] +pub struct AntiAliasingPlugin; +impl Plugin for AntiAliasingPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.add_plugins((FxaaPlugin, CasPlugin, SmaaPlugin)); + } +} diff --git a/crates/bevy_core_pipeline/src/smaa/SMAAAreaLUT.ktx2 b/crates/bevy_anti_aliasing/src/smaa/SMAAAreaLUT.ktx2 similarity index 100% rename from crates/bevy_core_pipeline/src/smaa/SMAAAreaLUT.ktx2 rename to crates/bevy_anti_aliasing/src/smaa/SMAAAreaLUT.ktx2 diff --git a/crates/bevy_core_pipeline/src/smaa/SMAASearchLUT.ktx2 b/crates/bevy_anti_aliasing/src/smaa/SMAASearchLUT.ktx2 similarity index 100% rename from crates/bevy_core_pipeline/src/smaa/SMAASearchLUT.ktx2 rename to crates/bevy_anti_aliasing/src/smaa/SMAASearchLUT.ktx2 diff --git a/crates/bevy_core_pipeline/src/smaa/mod.rs b/crates/bevy_anti_aliasing/src/smaa/mod.rs similarity index 97% rename from crates/bevy_core_pipeline/src/smaa/mod.rs rename to crates/bevy_anti_aliasing/src/smaa/mod.rs index 7dd371606c..0947abc5f8 100644 --- a/crates/bevy_core_pipeline/src/smaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/smaa/mod.rs @@ -29,16 +29,16 @@ //! * Compatibility with SSAA and MSAA. //! //! [SMAA]: https://www.iryoku.com/smaa/ -#[cfg(not(feature = "smaa_luts"))] -use crate::tonemapping::lut_placeholder; -use crate::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, -}; use bevy_app::{App, Plugin}; #[cfg(feature = "smaa_luts")] use bevy_asset::load_internal_binary_asset; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, weak_handle, Handle}; +#[cfg(not(feature = "smaa_luts"))] +use bevy_core_pipeline::tonemapping::lut_placeholder; +use bevy_core_pipeline::{ + core_2d::graph::{Core2d, Node2d}, + core_3d::graph::{Core3d, Node3d}, +}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -76,12 +76,10 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice, RenderQueue}, texture::{CachedTexture, GpuImage, TextureCache}, view::{ExtractedView, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_utils::prelude::default; -/// The handle of the `smaa.wgsl` shader. -const SMAA_SHADER_HANDLE: Handle = weak_handle!("fdd9839f-1ab4-4e0d-88a0-240b67da2ddf"); /// The handle of the area LUT, a KTX2 format texture that SMAA uses internally. const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle = weak_handle!("569c4d67-c7fa-4958-b1af-0836023603c0"); @@ -147,6 +145,8 @@ struct SmaaEdgeDetectionPipeline { postprocess_bind_group_layout: BindGroupLayout, /// The bind group layout for data specific to this pass. edge_detection_bind_group_layout: BindGroupLayout, + /// The shader asset handle. + shader: Handle, } /// The pipeline data for phase 2 of SMAA: blending weight calculation. @@ -155,6 +155,8 @@ struct SmaaBlendingWeightCalculationPipeline { postprocess_bind_group_layout: BindGroupLayout, /// The bind group layout for data specific to this pass. blending_weight_calculation_bind_group_layout: BindGroupLayout, + /// The shader asset handle. + shader: Handle, } /// The pipeline data for phase 3 of SMAA: neighborhood blending. @@ -163,6 +165,8 @@ struct SmaaNeighborhoodBlendingPipeline { postprocess_bind_group_layout: BindGroupLayout, /// The bind group layout for data specific to this pass. neighborhood_blending_bind_group_layout: BindGroupLayout, + /// The shader asset handle. + shader: Handle, } /// A unique identifier for a set of SMAA pipelines. @@ -287,7 +291,7 @@ pub struct SmaaSpecializedRenderPipelines { impl Plugin for SmaaPlugin { fn build(&self, app: &mut App) { // Load the shader. - load_internal_asset!(app, SMAA_SHADER_HANDLE, "smaa.wgsl", Shader::from_wgsl); + embedded_asset!(app, "smaa.wgsl"); // Load the two lookup textures. These are compressed textures in KTX2 // format. @@ -297,8 +301,6 @@ impl Plugin for SmaaPlugin { SMAA_AREA_LUT_TEXTURE_HANDLE, "SMAAAreaLUT.ktx2", |bytes, _: String| Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - "SMAAAreaLUT".to_owned(), bytes, bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2), bevy_image::CompressedImageFormats::NONE, @@ -315,8 +317,6 @@ impl Plugin for SmaaPlugin { SMAA_SEARCH_LUT_TEXTURE_HANDLE, "SMAASearchLUT.ktx2", |bytes, _: String| Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - "SMAASearchLUT".to_owned(), bytes, bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2), bevy_image::CompressedImageFormats::NONE, @@ -350,10 +350,10 @@ impl Plugin for SmaaPlugin { .add_systems( Render, ( - prepare_smaa_pipelines.in_set(RenderSet::Prepare), - prepare_smaa_uniforms.in_set(RenderSet::PrepareResources), - prepare_smaa_textures.in_set(RenderSet::PrepareResources), - prepare_smaa_bind_groups.in_set(RenderSet::PrepareBindGroups), + prepare_smaa_pipelines.in_set(RenderSystems::Prepare), + prepare_smaa_uniforms.in_set(RenderSystems::PrepareResources), + prepare_smaa_textures.in_set(RenderSystems::PrepareResources), + prepare_smaa_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) .add_render_graph_node::>(Core3d, Node3d::Smaa) @@ -435,18 +435,23 @@ impl FromWorld for SmaaPipelines { ), ); + let shader = load_embedded_asset!(world, "smaa.wgsl"); + SmaaPipelines { edge_detection: SmaaEdgeDetectionPipeline { postprocess_bind_group_layout: postprocess_bind_group_layout.clone(), edge_detection_bind_group_layout, + shader: shader.clone(), }, blending_weight_calculation: SmaaBlendingWeightCalculationPipeline { postprocess_bind_group_layout: postprocess_bind_group_layout.clone(), blending_weight_calculation_bind_group_layout, + shader: shader.clone(), }, neighborhood_blending: SmaaNeighborhoodBlendingPipeline { postprocess_bind_group_layout, neighborhood_blending_bind_group_layout, + shader, }, } } @@ -476,13 +481,13 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline { self.edge_detection_bind_group_layout.clone(), ], vertex: VertexState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: shader_defs.clone(), entry_point: "edge_detection_vertex_main".into(), buffers: vec![], }, fragment: Some(FragmentState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "luma_edge_detection_fragment_main".into(), targets: vec![Some(ColorTargetState { @@ -536,13 +541,13 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline { self.blending_weight_calculation_bind_group_layout.clone(), ], vertex: VertexState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: shader_defs.clone(), entry_point: "blending_weight_calculation_vertex_main".into(), buffers: vec![], }, fragment: Some(FragmentState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "blending_weight_calculation_fragment_main".into(), targets: vec![Some(ColorTargetState { @@ -584,13 +589,13 @@ impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline { self.neighborhood_blending_bind_group_layout.clone(), ], vertex: VertexState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: shader_defs.clone(), entry_point: "neighborhood_blending_vertex_main".into(), buffers: vec![], }, fragment: Some(FragmentState { - shader: SMAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "neighborhood_blending_fragment_main".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/smaa/smaa.wgsl b/crates/bevy_anti_aliasing/src/smaa/smaa.wgsl similarity index 100% rename from crates/bevy_core_pipeline/src/smaa/smaa.wgsl rename to crates/bevy_anti_aliasing/src/smaa/smaa.wgsl diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_anti_aliasing/src/taa/mod.rs similarity index 97% rename from crates/bevy_core_pipeline/src/taa/mod.rs rename to crates/bevy_anti_aliasing/src/taa/mod.rs index 9d69f1704a..efc5051680 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_anti_aliasing/src/taa/mod.rs @@ -1,11 +1,11 @@ -use crate::{ +use bevy_app::{App, Plugin}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; +use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, fullscreen_vertex_shader::fullscreen_shader_vertex_state, prelude::Camera3d, prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, }; -use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_diagnostic::FrameCount; use bevy_ecs::{ prelude::{Component, Entity, ReflectComponent}, @@ -36,12 +36,10 @@ use bevy_render::{ sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, view::{ExtractedView, Msaa, ViewTarget}, - ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, + ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems, }; use tracing::warn; -const TAA_SHADER_HANDLE: Handle = weak_handle!("fea20d50-86b6-4069-aa32-374346aec00c"); - /// Plugin for temporal anti-aliasing. /// /// See [`TemporalAntiAliasing`] for more details. @@ -49,7 +47,7 @@ pub struct TemporalAntiAliasPlugin; impl Plugin for TemporalAntiAliasPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl); + embedded_asset!(app, "taa.wgsl"); app.register_type::(); @@ -64,9 +62,9 @@ impl Plugin for TemporalAntiAliasPlugin { .add_systems( Render, ( - prepare_taa_jitter_and_mip_bias.in_set(RenderSet::ManageViews), - prepare_taa_pipelines.in_set(RenderSet::Prepare), - prepare_taa_history_textures.in_set(RenderSet::PrepareResources), + prepare_taa_jitter_and_mip_bias.in_set(RenderSystems::ManageViews), + prepare_taa_pipelines.in_set(RenderSystems::Prepare), + prepare_taa_history_textures.in_set(RenderSystems::PrepareResources), ), ) .add_render_graph_node::>(Core3d, Node3d::Taa) @@ -243,6 +241,7 @@ struct TaaPipeline { taa_bind_group_layout: BindGroupLayout, nearest_sampler: Sampler, linear_sampler: Sampler, + shader: Handle, } impl FromWorld for TaaPipeline { @@ -287,6 +286,7 @@ impl FromWorld for TaaPipeline { taa_bind_group_layout, nearest_sampler, linear_sampler, + shader: load_embedded_asset!(world, "taa.wgsl"), } } } @@ -319,7 +319,7 @@ impl SpecializedRenderPipeline for TaaPipeline { layout: vec![self.taa_bind_group_layout.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: TAA_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "taa".into(), targets: vec![ diff --git a/crates/bevy_core_pipeline/src/taa/taa.wgsl b/crates/bevy_anti_aliasing/src/taa/taa.wgsl similarity index 100% rename from crates/bevy_core_pipeline/src/taa/taa.wgsl rename to crates/bevy_anti_aliasing/src/taa/taa.wgsl diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 204b26958f..c892860dce 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -47,9 +47,8 @@ std = [ "bevy_ecs/std", "dep:ctrlc", "downcast-rs/std", - "bevy_utils/std", "bevy_tasks/std", - "bevy_platform_support/std", + "bevy_platform/std", ] ## `critical-section` provides the building blocks for synchronization primitives @@ -57,14 +56,14 @@ std = [ critical-section = [ "bevy_tasks/critical-section", "bevy_ecs/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", "bevy_reflect?/critical-section", ] ## Enables use of browser APIs. ## Note this is currently only applicable on `wasm32` architectures. web = [ - "bevy_platform_support/web", + "bevy_platform/web", "bevy_tasks/web", "bevy_reflect?/web", "dep:wasm-bindgen", @@ -77,11 +76,9 @@ web = [ bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } # other downcast-rs = { version = "2", default-features = false } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9ce162d217..61bbad3aed 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -10,14 +10,14 @@ use alloc::{ pub use bevy_derive::AppLabel; use bevy_ecs::{ component::RequiredComponentsError, - error::{BevyError, SystemErrorContext}, + error::{DefaultErrorHandler, ErrorHandler}, event::{event_update_system, EventCursor}, intern::Interned, prelude::*, schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput}, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe}; use log::debug; @@ -86,6 +86,7 @@ pub struct App { /// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html /// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html pub(crate) runner: RunnerFn, + default_error_handler: Option, } impl Debug for App { @@ -118,7 +119,7 @@ impl Default for App { app.add_systems( First, event_update_system - .in_set(bevy_ecs::event::EventUpdates) + .in_set(bevy_ecs::event::EventUpdateSystems) .run_if(bevy_ecs::event::event_update_condition), ); app.add_event::(); @@ -144,6 +145,7 @@ impl App { sub_apps: HashMap::default(), }, runner: Box::new(run_once), + default_error_handler: None, } } @@ -1116,7 +1118,12 @@ impl App { } /// Inserts a [`SubApp`] with the given label. - pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) { + pub fn insert_sub_app(&mut self, label: impl AppLabel, mut sub_app: SubApp) { + if let Some(handler) = self.default_error_handler { + sub_app + .world_mut() + .get_resource_or_insert_with(|| DefaultErrorHandler(handler)); + } self.sub_apps.sub_apps.insert(label.intern(), sub_app); } @@ -1274,18 +1281,6 @@ impl App { self } - /// Set the global system error handler to use for systems that return a [`Result`]. - /// - /// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error) - /// for more information. - pub fn set_system_error_handler( - &mut self, - error_handler: fn(BevyError, SystemErrorContext), - ) -> &mut Self { - self.main_mut().set_system_error_handler(error_handler); - self - } - /// Attempts to determine if an [`AppExit`] was raised since the last update. /// /// Will attempt to return the first [`Error`](AppExit::Error) it encounters. @@ -1311,6 +1306,8 @@ impl App { /// Spawns an [`Observer`] entity, which will watch for and respond to the given event. /// + /// `observer` can be any system whose first parameter is a [`Trigger`]. + /// /// # Examples /// /// ```rust @@ -1331,7 +1328,7 @@ impl App { /// # #[derive(Component)] /// # struct Friend; /// # - /// // An observer system can be any system where the first parameter is a trigger + /// /// app.add_observer(|trigger: Trigger, friends: Query>, mut commands: Commands| { /// if trigger.event().friends_allowed { /// for friend in friends.iter() { @@ -1347,6 +1344,49 @@ impl App { self.world_mut().add_observer(observer); self } + + /// Gets the error handler to set for new supapps. + /// + /// Note that the error handler of existing subapps may differ. + pub fn get_error_handler(&self) -> Option { + self.default_error_handler + } + + /// Set the [default error handler] for the all subapps (including the main one and future ones) + /// that do not have one. + /// + /// May only be called once and should be set by the application, not by libraries. + /// + /// The handler will be called when an error is produced and not otherwise handled. + /// + /// # Panics + /// Panics if called multiple times. + /// + /// # Example + /// ``` + /// # use bevy_app::*; + /// # use bevy_ecs::error::warn; + /// # fn MyPlugins(_: &mut App) {} + /// App::new() + /// .set_error_handler(warn) + /// .add_plugins(MyPlugins) + /// .run(); + /// ``` + /// + /// [default error handler]: bevy_ecs::error::DefaultErrorHandler + pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self { + assert!( + self.default_error_handler.is_none(), + "`set_error_handler` called multiple times on same `App`" + ); + self.default_error_handler = Some(handler); + for sub_app in self.sub_apps.iter_mut() { + sub_app + .world_mut() + .get_resource_or_insert_with(|| DefaultErrorHandler(handler)); + } + self + } } type RunnerFn = Box AppExit>; diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 5e4b87ff55..743806df71 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -55,9 +55,9 @@ pub mod prelude { main_schedule::{ First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop, - RunFixedMainLoopSystem, SpawnScene, Startup, Update, + RunFixedMainLoopSystems, SpawnScene, Startup, Update, }, sub_app::SubApp, - NonSendMarker, Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin, + Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin, }; } diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 483a828aab..7e0c759d47 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -15,6 +15,13 @@ use bevy_ecs::{ /// By default, it will run the following schedules in the given order: /// /// On the first run of the schedule (and only on the first run), it will run: +/// * [`StateTransition`] [^1] +/// * This means that [`OnEnter(MyState::Foo)`] will be called *before* [`PreStartup`] +/// if `MyState` was added to the app with `MyState::Foo` as the initial state, +/// as well as [`OnEnter(MyComputedState)`] if it `compute`s to `Some(Self)` in `MyState::Foo`. +/// * If you want to run systems before any state transitions, regardless of which state is the starting state, +/// for example, for registering required components, you can add your own custom startup schedule +/// before [`StateTransition`]. See [`MainScheduleOrder::insert_startup_before`] for more details. /// * [`PreStartup`] /// * [`Startup`] /// * [`PostStartup`] @@ -22,7 +29,7 @@ use bevy_ecs::{ /// Then it will run: /// * [`First`] /// * [`PreUpdate`] -/// * [`StateTransition`] +/// * [`StateTransition`] [^1] /// * [`RunFixedMainLoop`] /// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed. /// * [`Update`] @@ -37,35 +44,39 @@ use bevy_ecs::{ /// /// See [`RenderPlugin`] and [`PipelinedRenderingPlugin`] for more details. /// +/// [^1]: [`StateTransition`] is inserted only if you have `bevy_state` feature enabled. It is enabled in `default` features. +/// /// [`StateTransition`]: https://docs.rs/bevy/latest/bevy/prelude/struct.StateTransition.html +/// [`OnEnter(MyState::Foo)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html +/// [`OnEnter(MyComputedState)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html /// [`RenderPlugin`]: https://docs.rs/bevy/latest/bevy/render/struct.RenderPlugin.html /// [`PipelinedRenderingPlugin`]: https://docs.rs/bevy/latest/bevy/render/pipelined_rendering/struct.PipelinedRenderingPlugin.html /// [`SubApp`]: crate::SubApp -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Main; /// The schedule that runs before [`Startup`]. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct PreStartup; /// The schedule that runs once when the app starts. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Startup; /// The schedule that runs once after [`Startup`]. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct PostStartup; /// Runs first in the schedule. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct First; /// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard @@ -76,33 +87,33 @@ pub struct First; /// [`PreUpdate`] abstracts out "pre work implementation details". /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct PreUpdate; /// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed". /// -/// If you need to order your variable timestep systems -/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set. +/// If you need to order your variable timestep systems before or after +/// the fixed update logic, use the [`RunFixedMainLoopSystems`] system set. /// /// Note that in contrast to most other Bevy schedules, systems added directly to /// [`RunFixedMainLoop`] will *not* be parallelized between each other. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct RunFixedMainLoop; /// Runs first in the [`FixedMain`] schedule. /// /// See the [`FixedMain`] schedule for details on how fixed updates work. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedFirst; /// The schedule that contains logic that must run before [`FixedUpdate`]. /// /// See the [`FixedMain`] schedule for details on how fixed updates work. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedPreUpdate; /// The schedule that contains most gameplay logic, which runs at a fixed rate rather than every render frame. @@ -117,7 +128,7 @@ pub struct FixedPreUpdate; /// See the [`Update`] schedule for examples of systems that *should not* use this schedule. /// See the [`FixedMain`] schedule for details on how fixed updates work. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedUpdate; /// The schedule that runs after the [`FixedUpdate`] schedule, for reacting @@ -125,26 +136,26 @@ pub struct FixedUpdate; /// /// See the [`FixedMain`] schedule for details on how fixed updates work. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedPostUpdate; /// The schedule that runs last in [`FixedMain`] /// /// See the [`FixedMain`] schedule for details on how fixed updates work. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedLast; /// The schedule that contains systems which only run after a fixed period of time has elapsed. /// /// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems -/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set. +/// before or after the fixed update logic, use the [`RunFixedMainLoopSystems`] system set. /// /// 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). /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct FixedMain; /// The schedule that contains any app logic that must run once per render frame. @@ -157,13 +168,13 @@ pub struct FixedMain; /// /// See the [`FixedUpdate`] schedule for examples of systems that *should not* use this schedule. /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Update; /// The schedule that contains scene spawning. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct SpawnScene; /// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy @@ -174,18 +185,22 @@ pub struct SpawnScene; /// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`]. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct PostUpdate; /// Runs last in the schedule. /// /// See the [`Main`] schedule for some details about how schedules are run. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Last; /// Animation system set. This exists in [`PostUpdate`]. #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub struct Animation; +pub struct AnimationSystems; + +/// Deprecated alias for [`AnimationSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `AnimationSystems`.")] +pub type Animation = AnimationSystems; /// Defines the schedules to be run for the [`Main`] schedule, including /// their order. @@ -307,9 +322,9 @@ impl Plugin for MainSchedulePlugin { .configure_sets( RunFixedMainLoop, ( - RunFixedMainLoopSystem::BeforeFixedMainLoop, - RunFixedMainLoopSystem::FixedMainLoop, - RunFixedMainLoopSystem::AfterFixedMainLoop, + RunFixedMainLoopSystems::BeforeFixedMainLoop, + RunFixedMainLoopSystems::FixedMainLoop, + RunFixedMainLoopSystems::AfterFixedMainLoop, ) .chain(), ); @@ -389,7 +404,7 @@ impl FixedMain { /// Note that in contrast to most other Bevy schedules, systems added directly to /// [`RunFixedMainLoop`] will *not* be parallelized between each other. #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)] -pub enum RunFixedMainLoopSystem { +pub enum RunFixedMainLoopSystems { /// Runs before the fixed update logic. /// /// A good example of a system that fits here @@ -408,7 +423,7 @@ pub enum RunFixedMainLoopSystem { /// App::new() /// .add_systems( /// RunFixedMainLoop, - /// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop)) + /// update_camera_rotation.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop)) /// .add_systems(FixedUpdate, update_physics); /// /// # fn update_camera_rotation() {} @@ -421,7 +436,7 @@ pub enum RunFixedMainLoopSystem { /// /// Don't place systems here, use [`FixedUpdate`] and friends instead. /// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all - /// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`]. + /// other systems that run in [`RunFixedMainLoopSystems::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystems::AfterFixedMainLoop`]. /// /// [`Time`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html /// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep @@ -437,8 +452,8 @@ pub enum RunFixedMainLoopSystem { /// // This system will be called before all interpolation systems /// // that third-party plugins might add. /// prepare_for_interpolation - /// .after(RunFixedMainLoopSystem::FixedMainLoop) - /// .before(RunFixedMainLoopSystem::AfterFixedMainLoop), + /// .after(RunFixedMainLoopSystems::FixedMainLoop) + /// .before(RunFixedMainLoopSystems::AfterFixedMainLoop), /// ) /// ); /// @@ -462,10 +477,14 @@ pub enum RunFixedMainLoopSystem { /// .add_systems(FixedUpdate, update_physics) /// .add_systems( /// RunFixedMainLoop, - /// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop)); + /// interpolate_transforms.in_set(RunFixedMainLoopSystems::AfterFixedMainLoop)); /// /// # fn interpolate_transforms() {} /// # fn update_physics() {} /// ``` AfterFixedMainLoop, } + +/// Deprecated alias for [`RunFixedMainLoopSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `RunFixedMainLoopSystems`.")] +pub type RunFixedMainLoopSystem = RunFixedMainLoopSystems; diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index dca847f77f..60897d5453 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -4,7 +4,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use bevy_platform_support::collections::hash_map::Entry; +use bevy_platform::collections::hash_map::Entry; use bevy_utils::TypeIdMap; use core::any::TypeId; use log::{debug, warn}; diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index 6d10c92d76..594f849b2f 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -3,7 +3,7 @@ use crate::{ plugin::Plugin, PluginsState, }; -use bevy_platform_support::time::Instant; +use bevy_platform::time::Instant; use core::time::Duration; #[cfg(all(target_arch = "wasm32", feature = "web"))] @@ -159,9 +159,8 @@ impl Plugin for ScheduleRunnerPlugin { } else { loop { match tick(&mut app, wait) { - Ok(Some(_delay)) => { - #[cfg(feature = "std")] - std::thread::sleep(_delay); + Ok(Some(delay)) => { + bevy_platform::thread::sleep(delay); } Ok(None) => continue, Err(exit) => return exit, diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 7d0dfd3106..c340b80654 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -1,13 +1,12 @@ use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState}; use alloc::{boxed::Box, string::String, vec::Vec}; use bevy_ecs::{ - error::{DefaultSystemErrorHandler, SystemErrorContext}, event::EventRegistry, prelude::*, schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel}, system::{ScheduleSystem, SystemId, SystemInput}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use core::fmt::Debug; #[cfg(feature = "trace")] @@ -336,22 +335,6 @@ impl SubApp { self } - /// Set the global error handler to use for systems that return a [`Result`]. - /// - /// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error) - /// for more information. - pub fn set_system_error_handler( - &mut self, - error_handler: fn(BevyError, SystemErrorContext), - ) -> &mut Self { - let mut default_handler = self - .world_mut() - .get_resource_or_init::(); - - default_handler.0 = error_handler; - self - } - /// See [`App::add_event`]. pub fn add_event(&mut self) -> &mut Self where diff --git a/crates/bevy_app/src/task_pool_plugin.rs b/crates/bevy_app/src/task_pool_plugin.rs index aa1dd23fd5..5ed4e3fa5d 100644 --- a/crates/bevy_app/src/task_pool_plugin.rs +++ b/crates/bevy_app/src/task_pool_plugin.rs @@ -1,20 +1,21 @@ use crate::{App, Plugin}; use alloc::string::ToString; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder}; -use core::{fmt::Debug, marker::PhantomData}; +use core::fmt::Debug; use log::trace; cfg_if::cfg_if! { if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] { - use {crate::Last, bevy_ecs::prelude::NonSend, bevy_tasks::tick_global_task_pools_on_main_thread}; + use {crate::Last, bevy_tasks::tick_global_task_pools_on_main_thread}; + use bevy_ecs::system::NonSendMarker; /// A system used to check and advanced our task pools. /// /// Calls [`tick_global_task_pools_on_main_thread`], /// and uses [`NonSendMarker`] to ensure that this system runs on the main thread - fn tick_global_task_pools(_main_thread_marker: Option>) { + fn tick_global_task_pools(_main_thread_marker: NonSendMarker) { tick_global_task_pools_on_main_thread(); } } @@ -36,8 +37,6 @@ impl Plugin for TaskPoolPlugin { _app.add_systems(Last, tick_global_task_pools); } } -/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread. -pub struct NonSendMarker(PhantomData<*mut ()>); /// Defines a simple way to determine how many threads to use given the number of remaining cores /// and number of total cores diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 0c1576561e..07a45a3f6d 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["bevy"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -file_watcher = ["notify-debouncer-full", "watch"] +file_watcher = ["notify-debouncer-full", "watch", "multi_threaded"] embedded_watcher = ["file_watcher"] multi_threaded = ["bevy_tasks/multi_threaded"] asset_processor = [] @@ -19,39 +19,50 @@ watch = [] trace = [] [dependencies] -bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ + "bevy_reflect", +] } bevy_asset_macros = { path = "macros", version = "0.16.0-dev" } -bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } -bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ "uuid", ] } -bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [ + "async_executor", +] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } -stackfuture = "0.3" -atomicow = "1.0" -async-broadcast = "0.7.2" -async-fs = "2.0" -async-lock = "3.0" -bitflags = { version = "2.3", features = ["serde"] } -crossbeam-channel = "0.5" -downcast-rs = { version = "2", default-features = false, features = ["std"] } -disqualified = "1.0" -either = "1.13" -futures-io = "0.3" -futures-lite = "2.0.1" -blake3 = "1.5" -parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] } -ron = "0.8" -serde = { version = "1", features = ["derive"] } +stackfuture = { version = "0.3", default-features = false } +atomicow = { version = "1.1", default-features = false, features = ["std"] } +async-broadcast = { version = "0.7.2", default-features = false } +async-fs = { version = "2.0", default-features = false } +async-lock = { version = "3.0", default-features = false } +bitflags = { version = "2.3", default-features = false } +crossbeam-channel = { version = "0.5", default-features = false, features = [ + "std", +] } +downcast-rs = { version = "2", default-features = false } +disqualified = { version = "1.0", default-features = false } +either = { version = "1.13", default-features = false } +futures-io = { version = "0.3", default-features = false } +futures-lite = { version = "2.0.1", default-features = false } +blake3 = { version = "1.5", default-features = false } +parking_lot = { version = "0.12", default-features = false, features = [ + "arc_lock", + "send_guard", +] } +ron = { version = "0.8", default-features = false } +serde = { version = "1", default-features = false, features = ["derive"] } thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = ["from"] } -uuid = { version = "1.13.1", features = ["v4"] } -tracing = { version = "0.1", default-features = false, features = ["std"] } +uuid = { version = "1.13.1", default-features = false, features = [ + "v4", + "serde", +] } +tracing = { version = "0.1", default-features = false } [target.'cfg(target_os = "android")'.dependencies] bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } @@ -78,7 +89,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu ] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -notify-debouncer-full = { version = "0.5.0", optional = true } +notify-debouncer-full = { version = "0.5.0", default-features = false, optional = true } [lints] workspace = true diff --git a/crates/bevy_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index f8501cb206..d314fa3fd6 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -13,7 +13,7 @@ use bevy_ecs::{ storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::marker::PhantomData; use disqualified::ShortName; use tracing::error; @@ -22,10 +22,10 @@ use tracing::error; /// the [`AssetChanged`] filter to determine if an asset has changed since the last time /// a query ran. /// -/// This resource is automatically managed by the [`AssetEvents`](crate::AssetEvents) schedule and -/// should not be exposed to the user in order to maintain safety guarantees. Any additional uses of -/// this resource should be carefully audited to ensure that they do not introduce any safety -/// issues. +/// This resource is automatically managed by the [`AssetEventSystems`](crate::AssetEventSystems) +/// system set and should not be exposed to the user in order to maintain safety guarantees. +/// Any additional uses of this resource should be carefully audited to ensure that they do not +/// introduce any safety issues. #[derive(Resource)] pub(crate) struct AssetChanges { change_ticks: HashMap, Tick>, @@ -102,14 +102,13 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// /// # Quirks /// -/// - Asset changes are registered in the [`AssetEvents`] schedule. +/// - Asset changes are registered in the [`AssetEventSystems`] system set. /// - Removed assets are not detected. /// -/// The list of changed assets only gets updated in the -/// [`AssetEvents`] schedule which runs in `Last`. Therefore, `AssetChanged` -/// will only pick up asset changes in schedules following `AssetEvents` or the -/// next frame. Consider adding the system in the `Last` schedule after [`AssetEvents`] if you need -/// to react without frame delay to asset changes. +/// The list of changed assets only gets updated in the [`AssetEventSystems`] system set, +/// which runs in `PostUpdate`. Therefore, `AssetChanged` will only pick up asset changes in schedules +/// following [`AssetEventSystems`] or the next frame. Consider adding the system in the `Last` schedule +/// after [`AssetEventSystems`] if you need to react without frame delay to asset changes. /// /// # Performance /// @@ -120,7 +119,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> { /// /// If no `A` asset updated since the last time the system ran, then no lookups occur. /// -/// [`AssetEvents`]: crate::AssetEvents +/// [`AssetEventSystems`]: crate::AssetEventSystems /// [`Assets::get_mut`]: crate::Assets::get_mut pub struct AssetChanged(PhantomData); @@ -166,7 +165,7 @@ unsafe impl WorldQuery for AssetChanged { this_run: Tick, ) -> Self::Fetch<'w> { // SAFETY: - // - `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule + // - `AssetChanges` is private and only accessed mutably in the `AssetEventSystems` system set. // - `resource_id` was obtained from the type ID of `AssetChanges`. let Some(changes) = (unsafe { world @@ -283,7 +282,7 @@ unsafe impl QueryFilter for AssetChanged { #[cfg(test)] #[expect(clippy::print_stdout, reason = "Allowed in tests.")] mod tests { - use crate::{AssetEvents, AssetPlugin, Handle}; + use crate::{AssetEventSystems, AssetPlugin, Handle}; use alloc::{vec, vec::Vec}; use core::num::NonZero; use std::println; @@ -406,7 +405,7 @@ mod tests { .init_asset::() .insert_resource(Counter(vec![0, 0, 0, 0])) .add_systems(Update, add_some) - .add_systems(PostUpdate, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEventSystems)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 @@ -441,7 +440,7 @@ mod tests { }, ) .add_systems(Update, update_some) - .add_systems(PostUpdate, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEventSystems)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 739b6b087e..9fa8eb4381 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ resource::Resource, system::{Res, ResMut, SystemChangeTick}, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{Reflect, TypePath}; use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32}; use crossbeam_channel::{Receiver, Sender}; @@ -462,16 +462,22 @@ impl Assets { /// Removes the [`Asset`] with the given `id`. pub(crate) fn remove_dropped(&mut self, id: AssetId) { match self.duplicate_handles.get_mut(&id) { - None | Some(0) => {} + None => {} + Some(0) => { + self.duplicate_handles.remove(&id); + } Some(value) => { *value -= 1; return; } } + let existed = match id { AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index).is_some(), AssetId::Uuid { uuid } => self.hash_map.remove(&uuid).is_some(), }; + + self.queued_events.push(AssetEvent::Unused { id }); if existed { self.queued_events.push(AssetEvent::Removed { id }); } @@ -553,7 +559,6 @@ impl Assets { } } - assets.queued_events.push(AssetEvent::Unused { id }); assets.remove_dropped(id); } } @@ -595,7 +600,7 @@ impl Assets { pub struct AssetsMutIterator<'a, A: Asset> { queued_events: &'a mut Vec>, dense_storage: Enumerate>>, - hash_map: bevy_platform_support::collections::hash_map::IterMut<'a, Uuid, A>, + hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>, } impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> { diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index afed919568..e6f00c7da5 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -11,7 +11,6 @@ use core::{ use crossbeam_channel::{Receiver, Sender}; use disqualified::ShortName; use thiserror::Error; -use uuid::Uuid; /// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_. /// This should _only_ be used for one specific asset type. @@ -149,14 +148,6 @@ impl Clone for Handle { } impl Handle { - /// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`]. - #[deprecated = "use the `weak_handle!` macro with a UUID string instead"] - pub const fn weak_from_u128(value: u128) -> Self { - Handle::Weak(AssetId::Uuid { - uuid: Uuid::from_u128(value), - }) - } - /// Returns the [`AssetId`] of this [`Asset`]. #[inline] pub fn id(&self) -> AssetId { @@ -289,7 +280,7 @@ impl From<&mut Handle> for UntypedAssetId { /// to be stored together and compared. /// /// See [`Handle`] for more information. -#[derive(Clone)] +#[derive(Clone, Reflect)] pub enum UntypedHandle { /// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped. Strong(Arc), @@ -548,9 +539,10 @@ pub enum UntypedAssetConversionError { #[cfg(test)] mod tests { use alloc::boxed::Box; - use bevy_platform_support::hash::FixedHasher; + use bevy_platform::hash::FixedHasher; use bevy_reflect::PartialReflect; use core::hash::BuildHasher; + use uuid::Uuid; use super::*; diff --git a/crates/bevy_asset/src/id.rs b/crates/bevy_asset/src/id.rs index 0f146eda65..78686e31c2 100644 --- a/crates/bevy_asset/src/id.rs +++ b/crates/bevy_asset/src/id.rs @@ -164,7 +164,7 @@ impl From for AssetId { /// 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)] +#[derive(Debug, Copy, Clone, Reflect)] 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 @@ -441,7 +441,7 @@ mod tests { fn hash(data: &T) -> u64 { use core::hash::BuildHasher; - bevy_platform_support::hash::FixedHasher.hash_one(data) + bevy_platform::hash::FixedHasher.hash_one(data) } /// Typed and Untyped `AssetIds` should be equivalent to each other and themselves diff --git a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs index 89ac9b3826..f7fb56be74 100644 --- a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs +++ b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs @@ -4,7 +4,7 @@ use crate::io::{ AssetSourceEvent, AssetWatcher, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::time::Duration; use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache}; use parking_lot::RwLock; diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index fce5149462..f6c44397fc 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -8,8 +8,10 @@ use crate::io::{ memory::{Dir, MemoryAssetReader, Value}, AssetSource, AssetSourceBuilders, }; +use crate::AssetServer; use alloc::boxed::Box; -use bevy_ecs::resource::Resource; +use bevy_app::App; +use bevy_ecs::{resource::Resource, world::World}; use std::path::{Path, PathBuf}; #[cfg(feature = "embedded_watcher")] @@ -29,7 +31,7 @@ pub struct EmbeddedAssetRegistry { dir: Dir, #[cfg(feature = "embedded_watcher")] root_paths: alloc::sync::Arc< - parking_lot::RwLock, PathBuf>>, + parking_lot::RwLock, PathBuf>>, >, } @@ -132,6 +134,71 @@ impl EmbeddedAssetRegistry { } } +/// Trait for the [`load_embedded_asset!`] macro, to access [`AssetServer`] +/// from arbitrary things. +/// +/// [`load_embedded_asset!`]: crate::load_embedded_asset +pub trait GetAssetServer { + fn get_asset_server(&self) -> &AssetServer; +} +impl GetAssetServer for App { + fn get_asset_server(&self) -> &AssetServer { + self.world().get_asset_server() + } +} +impl GetAssetServer for World { + fn get_asset_server(&self) -> &AssetServer { + self.resource() + } +} +impl GetAssetServer for AssetServer { + fn get_asset_server(&self) -> &AssetServer { + self + } +} + +/// Load an [embedded asset](crate::embedded_asset). +/// +/// This is useful if the embedded asset in question is not publicly exposed, but +/// you need to use it internally. +/// +/// # Syntax +/// +/// This macro takes two arguments and an optional third one: +/// 1. The asset source. It may be `AssetServer`, `World` or `App`. +/// 2. The path to the asset to embed, as a string literal. +/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`]. +/// Consider explicitly typing the closure argument in case of type error. +/// +/// # Usage +/// +/// The advantage compared to using directly [`AssetServer::load`] is: +/// - This also accepts [`World`] and [`App`] arguments. +/// - This uses the exact same path as `embedded_asset!`, so you can keep it +/// consistent. +/// +/// As a rule of thumb: +/// - If the asset in used in the same module as it is declared using `embedded_asset!`, +/// use this macro. +/// - Otherwise, use `AssetServer::load`. +#[macro_export] +macro_rules! load_embedded_asset { + (@get: $path: literal, $provider: expr) => {{ + let path = $crate::embedded_path!($path); + let path = $crate::AssetPath::from_path_buf(path).with_source("embedded"); + let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider); + (path, asset_server) + }}; + ($provider: expr, $path: literal, $settings: expr) => {{ + let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider); + asset_server.load_with_settings(path, $settings) + }}; + ($provider: expr, $path: literal) => {{ + let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider); + asset_server.load(path) + }}; +} + /// Returns the [`Path`] for a given `embedded` asset. /// This is used internally by [`embedded_asset`] and can be used to get a [`Path`] /// that matches the [`AssetPath`](crate::AssetPath) used by that asset. @@ -140,7 +207,7 @@ impl EmbeddedAssetRegistry { #[macro_export] macro_rules! embedded_path { ($path_str: expr) => {{ - embedded_path!("src", $path_str) + $crate::embedded_path!("src", $path_str) }}; ($source_path: expr, $path_str: expr) => {{ @@ -168,6 +235,13 @@ pub fn _embedded_asset_path( file_path: &Path, asset_path: &Path, ) -> PathBuf { + let file_path = if cfg!(not(target_family = "windows")) { + // Work around bug: https://github.com/bevyengine/bevy/issues/14246 + // Note, this will break any paths on Linux/Mac containing "\" + PathBuf::from(file_path.to_str().unwrap().replace("\\", "/")) + } else { + PathBuf::from(file_path) + }; let mut maybe_parent = file_path.parent(); let after_src = loop { let Some(parent) = maybe_parent else { @@ -185,7 +259,7 @@ pub fn _embedded_asset_path( /// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary /// and registering those bytes with the `embedded` [`AssetSource`]. /// -/// This accepts the current [`App`](bevy_app::App) as the first parameter and a path `&str` (relative to the current file) as the second. +/// This accepts the current [`App`] as the first parameter and a path `&str` (relative to the current file) as the second. /// /// By default this will generate an [`AssetPath`] using the following rules: /// @@ -210,14 +284,19 @@ pub fn _embedded_asset_path( /// /// `embedded_asset!(app, "rock.wgsl")` /// -/// `rock.wgsl` can now be loaded by the [`AssetServer`](crate::AssetServer) with the following path: +/// `rock.wgsl` can now be loaded by the [`AssetServer`] as follows: /// /// ```no_run -/// # use bevy_asset::{Asset, AssetServer}; +/// # use bevy_asset::{Asset, AssetServer, load_embedded_asset}; /// # use bevy_reflect::TypePath; /// # let asset_server: AssetServer = panic!(); /// # #[derive(Asset, TypePath)] /// # struct Shader; +/// // If we are loading the shader in the same module we used `embedded_asset!`: +/// let shader = load_embedded_asset!(&asset_server, "rock.wgsl"); +/// # let _: bevy_asset::Handle = shader; +/// +/// // If the goal is to expose the asset **to the end user**: /// let shader = asset_server.load::("embedded://bevy_rock/render/rock.wgsl"); /// ``` /// @@ -251,11 +330,11 @@ pub fn _embedded_asset_path( /// [`embedded_path`]: crate::embedded_path #[macro_export] macro_rules! embedded_asset { - ($app: ident, $path: expr) => {{ + ($app: expr, $path: expr) => {{ $crate::embedded_asset!($app, "src", $path) }}; - ($app: ident, $source_path: expr, $path: expr) => {{ + ($app: expr, $source_path: expr, $path: expr) => {{ let mut embedded = $app .world_mut() .resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>(); diff --git a/crates/bevy_asset/src/io/file/file_asset.rs b/crates/bevy_asset/src/io/file/file_asset.rs index f65680217b..58ae546e7f 100644 --- a/crates/bevy_asset/src/io/file/file_asset.rs +++ b/crates/bevy_asset/src/io/file/file_asset.rs @@ -74,6 +74,15 @@ impl AssetReader for FileAssetReader { return None; } } + // filter out hidden files. they are not listed by default but are directly targetable + if path + .file_name() + .and_then(|file_name| file_name.to_str()) + .map(|file_name| file_name.starts_with('.')) + .unwrap_or_default() + { + return None; + } let relative_path = path.strip_prefix(&root_path).unwrap(); Some(relative_path.to_owned()) }) diff --git a/crates/bevy_asset/src/io/file/file_watcher.rs b/crates/bevy_asset/src/io/file/file_watcher.rs index f1a26853c4..e70cf1665f 100644 --- a/crates/bevy_asset/src/io/file/file_watcher.rs +++ b/crates/bevy_asset/src/io/file/file_watcher.rs @@ -35,7 +35,7 @@ impl FileWatcher { sender: Sender, debounce_wait_time: Duration, ) -> Result { - let root = normalize_path(&path).canonicalize().unwrap(); + let root = normalize_path(&path).canonicalize()?; let watcher = new_asset_event_debouncer( path.clone(), debounce_wait_time, @@ -262,7 +262,7 @@ impl FilesystemEventHandler for FileEventHandler { self.last_event = None; } fn get_path(&self, absolute_path: &Path) -> Option<(PathBuf, bool)> { - let absolute_path = absolute_path.canonicalize().unwrap(); + let absolute_path = absolute_path.canonicalize().ok()?; Some(get_asset_path(&self.root, &absolute_path)) } diff --git a/crates/bevy_asset/src/io/file/sync_file_asset.rs b/crates/bevy_asset/src/io/file/sync_file_asset.rs index 7533256204..9281d323c8 100644 --- a/crates/bevy_asset/src/io/file/sync_file_asset.rs +++ b/crates/bevy_asset/src/io/file/sync_file_asset.rs @@ -145,6 +145,16 @@ impl AssetReader for FileAssetReader { return None; } } + // filter out hidden files. they are not listed by default but are directly targetable + if path + .file_name() + .and_then(|file_name| file_name.to_str()) + .map(|file_name| file_name.starts_with('.')) + .unwrap_or_default() + { + return None; + } + let relative_path = path.strip_prefix(&root_path).unwrap(); Some(relative_path.to_owned()) }) diff --git a/crates/bevy_asset/src/io/gated.rs b/crates/bevy_asset/src/io/gated.rs index ab3e0998e2..fa4f0f0d3f 100644 --- a/crates/bevy_asset/src/io/gated.rs +++ b/crates/bevy_asset/src/io/gated.rs @@ -1,6 +1,6 @@ use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use alloc::{boxed::Box, sync::Arc}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use crossbeam_channel::{Receiver, Sender}; use parking_lot::RwLock; use std::path::Path; diff --git a/crates/bevy_asset/src/io/memory.rs b/crates/bevy_asset/src/io/memory.rs index 5b9e5389c2..4c56057ff9 100644 --- a/crates/bevy_asset/src/io/memory.rs +++ b/crates/bevy_asset/src/io/memory.rs @@ -1,6 +1,6 @@ use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::{pin::Pin, task::Poll}; use futures_io::AsyncRead; use futures_lite::{ready, Stream}; @@ -60,8 +60,7 @@ impl Dir { dir = self.get_or_insert_dir(parent); } let key: Box = path.file_name().unwrap().to_string_lossy().into(); - let data = dir.0.write().assets.remove(&key); - data + dir.0.write().assets.remove(&key) } pub fn insert_meta(&self, path: &Path, value: impl Into) { diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index 58c5006a80..4852a2a71f 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -9,7 +9,7 @@ use alloc::{ }; use atomicow::CowArc; use bevy_ecs::resource::Resource; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::{fmt::Display, hash::Hash, time::Duration}; use thiserror::Error; use tracing::{error, warn}; diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index 125543fc11..c2551a40f1 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -52,7 +52,7 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ } impl HttpWasmAssetReader { - async fn fetch_bytes<'a>(&self, path: PathBuf) -> Result { + async fn fetch_bytes(&self, path: PathBuf) -> Result { // The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available. let global: Global = js_sys::global().unchecked_into(); let promise = if !global.window().is_undefined() { diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index d008c48b03..5b680eb191 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -223,18 +223,11 @@ use bevy_ecs::{ schedule::{IntoScheduleConfigs, SystemSet}, world::FromWorld, }; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath}; use core::any::TypeId; use tracing::error; -#[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. /// @@ -258,6 +251,33 @@ pub struct AssetPlugin { pub mode: AssetMode, /// How/If asset meta files should be checked. pub meta_check: AssetMetaCheck, + /// How to handle load requests of files that are outside the approved directories. + /// + /// Approved folders are [`AssetPlugin::file_path`] and the folder of each + /// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid. + pub unapproved_path_mode: UnapprovedPathMode, +} + +/// Determines how to react to attempts to load assets not inside the approved folders. +/// +/// Approved folders are [`AssetPlugin::file_path`] and the folder of each +/// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid. +/// +/// It is strongly discouraged to use [`Allow`](UnapprovedPathMode::Allow) if your +/// app will include scripts or modding support, as it could allow allow arbitrary file +/// access for malicious code. +/// +/// See [`AssetPath::is_unapproved`](crate::AssetPath::is_unapproved) +#[derive(Clone, Default)] +pub enum UnapprovedPathMode { + /// Unapproved asset loading is allowed. This is strongly discouraged. + Allow, + /// Fails to load any asset that is is unapproved, unless an override method is used, like + /// [`AssetServer::load_override`]. + Deny, + /// Fails to load any asset that is is unapproved. + #[default] + Forbid, } /// Controls whether or not assets are pre-processed before being loaded. @@ -311,6 +331,7 @@ impl Default for AssetPlugin { processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(), watch_for_changes_override: None, meta_check: AssetMetaCheck::default(), + unapproved_path_mode: UnapprovedPathMode::default(), } } } @@ -351,6 +372,7 @@ impl Plugin for AssetPlugin { AssetServerMode::Unprocessed, self.meta_check.clone(), watch, + self.unapproved_path_mode.clone(), )); } AssetMode::Processed => { @@ -367,6 +389,7 @@ impl Plugin for AssetPlugin { AssetServerMode::Processed, AssetMetaCheck::Always, watch, + self.unapproved_path_mode.clone(), )) .insert_resource(processor) .add_systems(bevy_app::Startup, AssetProcessor::start); @@ -380,6 +403,7 @@ impl Plugin for AssetPlugin { AssetServerMode::Processed, AssetMetaCheck::Always, watch, + self.unapproved_path_mode.clone(), )); } } @@ -390,7 +414,10 @@ impl Plugin for AssetPlugin { .init_asset::() .init_asset::<()>() .add_event::() - .configure_sets(PreUpdate, TrackAssets.after(handle_internal_asset_events)) + .configure_sets( + PreUpdate, + AssetTrackingSystems.after(handle_internal_asset_events), + ) // `handle_internal_asset_events` requires the use of `&mut World`, // and as a result has ambiguous system ordering with all other systems in `PreUpdate`. // This is virtually never a real problem: asset loading is async and so anything that interacts directly with it @@ -587,9 +614,12 @@ impl AssetApp for App { PostUpdate, Assets::::asset_events .run_if(Assets::::asset_events_condition) - .in_set(AssetEvents), + .in_set(AssetEventSystems), + ) + .add_systems( + PreUpdate, + Assets::::track_assets.in_set(AssetTrackingSystems), ) - .add_systems(PreUpdate, Assets::::track_assets.in_set(TrackAssets)) } fn register_asset_reflect(&mut self) -> &mut Self @@ -619,13 +649,21 @@ impl AssetApp for App { /// A system set that holds all "track asset" operations. #[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)] -pub struct TrackAssets; +pub struct AssetTrackingSystems; + +/// Deprecated alias for [`AssetTrackingSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `AssetTrackingSystems`.")] +pub type TrackAssets = AssetTrackingSystems; /// A system set where events accumulated in [`Assets`] are applied to the [`AssetEvent`] [`Events`] resource. /// /// [`Events`]: bevy_ecs::event::Events #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub struct AssetEvents; +pub struct AssetEventSystems; + +/// Deprecated alias for [`AssetEventSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `AssetEventSystems`.")] +pub type AssetEvents = AssetEventSystems; #[cfg(test)] mod tests { @@ -639,7 +677,7 @@ mod tests { }, loader::{AssetLoader, LoadContext}, Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath, - AssetPlugin, AssetServer, Assets, DuplicateLabelAssetError, LoadState, + AssetPlugin, AssetServer, Assets, LoadState, UnapprovedPathMode, }; use alloc::{ boxed::Box, @@ -655,8 +693,7 @@ mod tests { prelude::*, schedule::{LogLevel, ScheduleBuildSettings}, }; - use bevy_log::LogPlugin; - use bevy_platform_support::collections::HashMap; + use bevy_platform::collections::HashMap; use bevy_reflect::TypePath; use core::time::Duration; use serde::{Deserialize, Serialize}; @@ -695,8 +732,6 @@ mod tests { CannotLoadDependency { dependency: AssetPath<'static> }, #[error("A RON error occurred during loading")] RonSpannedError(#[from] ron::error::SpannedError), - #[error(transparent)] - DuplicateLabelAssetError(#[from] DuplicateLabelAssetError), #[error("An IO error occurred during loading")] Io(#[from] std::io::Error), } @@ -727,7 +762,7 @@ mod tests { .map_err(|_| Self::Error::CannotLoadDependency { dependency: dep.into(), })?; - let cool = loaded.get_asset().get(); + let cool = loaded.get(); embedded.push_str(&cool.text); } Ok(CoolText { @@ -742,7 +777,7 @@ mod tests { .sub_texts .drain(..) .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text })) - .collect::, _>>()?, + .collect(), }) } @@ -826,11 +861,7 @@ mod tests { AssetSourceId::Default, AssetSource::build().with_reader(move || Box::new(gated_memory_reader.clone())), ) - .add_plugins(( - TaskPoolPlugin::default(), - LogPlugin::default(), - AssetPlugin::default(), - )); + .add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())); (app, gate_opener) } @@ -1728,11 +1759,7 @@ mod tests { "unstable", AssetSource::build().with_reader(move || Box::new(unstable_reader.clone())), ) - .add_plugins(( - TaskPoolPlugin::default(), - LogPlugin::default(), - AssetPlugin::default(), - )) + .add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())) .init_asset::() .register_asset_loader(CoolTextLoader) .init_resource::() @@ -1780,49 +1807,6 @@ mod tests { app.world_mut().run_schedule(Update); } - #[test] - fn fails_to_load_for_duplicate_subasset_labels() { - let mut app = App::new(); - - let dir = Dir::default(); - dir.insert_asset_text( - Path::new("a.ron"), - r#"( - text: "b", - dependencies: [], - embedded_dependencies: [], - sub_texts: ["A", "A"], -)"#, - ); - - app.register_asset_source( - AssetSourceId::Default, - AssetSource::build() - .with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })), - ) - .add_plugins(( - TaskPoolPlugin::default(), - LogPlugin::default(), - AssetPlugin::default(), - )); - - app.init_asset::() - .init_asset::() - .register_asset_loader(CoolTextLoader); - - let asset_server = app.world().resource::().clone(); - let handle = asset_server.load::("a.ron"); - - run_app_until(&mut app, |_world| match asset_server.load_state(&handle) { - LoadState::Loading => None, - LoadState::Failed(err) => { - assert!(matches!(*err, AssetLoadError::AssetLoaderError(_))); - Some(()) - } - state => panic!("Unexpected asset state: {state:?}"), - }); - } - // This test is not checking a requirement, but documenting a current limitation. We simply are // not capable of loading subassets when doing nested immediate loads. #[test] @@ -1846,11 +1830,7 @@ mod tests { AssetSource::build() .with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })), ) - .add_plugins(( - TaskPoolPlugin::default(), - LogPlugin::default(), - AssetPlugin::default(), - )); + .add_plugins((TaskPoolPlugin::default(), AssetPlugin::default())); app.init_asset::() .init_asset::() @@ -1933,4 +1913,91 @@ mod tests { #[derive(Asset, TypePath)] pub struct TupleTestAsset(#[dependency] Handle); + + fn unapproved_path_setup(mode: UnapprovedPathMode) -> App { + let dir = Dir::default(); + let a_path = "../a.cool.ron"; + let a_ron = r#" +( + text: "a", + dependencies: [], + embedded_dependencies: [], + sub_texts: [], +)"#; + + dir.insert_asset_text(Path::new(a_path), a_ron); + + let mut app = App::new(); + let memory_reader = MemoryAssetReader { root: dir }; + app.register_asset_source( + AssetSourceId::Default, + AssetSource::build().with_reader(move || Box::new(memory_reader.clone())), + ) + .add_plugins(( + TaskPoolPlugin::default(), + AssetPlugin { + unapproved_path_mode: mode, + ..Default::default() + }, + )); + app.init_asset::(); + + app + } + + fn load_a_asset(assets: Res) { + let a = assets.load::("../a.cool.ron"); + if a == Handle::default() { + panic!() + } + } + + fn load_a_asset_override(assets: Res) { + let a = assets.load_override::("../a.cool.ron"); + if a == Handle::default() { + panic!() + } + } + + #[test] + #[should_panic] + fn unapproved_path_forbid_should_panic() { + let mut app = unapproved_path_setup(UnapprovedPathMode::Forbid); + + fn uses_assets(_asset: ResMut>) {} + app.add_systems(Update, (uses_assets, load_a_asset_override)); + + app.world_mut().run_schedule(Update); + } + + #[test] + #[should_panic] + fn unapproved_path_deny_should_panic() { + let mut app = unapproved_path_setup(UnapprovedPathMode::Deny); + + fn uses_assets(_asset: ResMut>) {} + app.add_systems(Update, (uses_assets, load_a_asset)); + + app.world_mut().run_schedule(Update); + } + + #[test] + fn unapproved_path_deny_should_finish() { + let mut app = unapproved_path_setup(UnapprovedPathMode::Deny); + + fn uses_assets(_asset: ResMut>) {} + app.add_systems(Update, (uses_assets, load_a_asset_override)); + + app.world_mut().run_schedule(Update); + } + + #[test] + fn unapproved_path_allow_should_finish() { + let mut app = unapproved_path_setup(UnapprovedPathMode::Allow); + + fn uses_assets(_asset: ResMut>) {} + app.add_systems(Update, (uses_assets, load_a_asset)); + + app.world_mut().run_schedule(Update); + } } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 8b02e5b3db..8f4863b885 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -13,7 +13,7 @@ use alloc::{ }; use atomicow::CowArc; use bevy_ecs::world::World; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use core::any::{Any, TypeId}; use downcast_rs::{impl_downcast, Downcast}; @@ -60,7 +60,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static { load_context: LoadContext<'a>, ) -> BoxedFuture< 'a, - Result>, + Result>, >; /// Returns a list of extensions supported by this asset loader, without the preceding dot. @@ -91,7 +91,7 @@ where mut load_context: LoadContext<'a>, ) -> BoxedFuture< 'a, - Result>, + Result>, > { Box::pin(async move { let settings = meta @@ -152,6 +152,7 @@ pub struct LoadedAsset { pub(crate) value: A, pub(crate) dependencies: HashSet, pub(crate) loader_dependencies: HashMap, AssetHash>, + pub(crate) labeled_assets: HashMap, LabeledAsset>, } impl LoadedAsset { @@ -165,6 +166,7 @@ impl LoadedAsset { value, dependencies, loader_dependencies: HashMap::default(), + labeled_assets: HashMap::default(), } } @@ -177,6 +179,19 @@ impl LoadedAsset { pub fn get(&self) -> &A { &self.value } + + /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists. + pub fn get_labeled( + &self, + label: impl Into>, + ) -> Option<&ErasedLoadedAsset> { + self.labeled_assets.get(&label.into()).map(|a| &a.asset) + } + + /// Iterate over all labels for "labeled assets" in the loaded asset + pub fn iter_labels(&self) -> impl Iterator { + self.labeled_assets.keys().map(|s| &**s) + } } impl From for LoadedAsset { @@ -190,6 +205,7 @@ pub struct ErasedLoadedAsset { pub(crate) value: Box, pub(crate) dependencies: HashSet, pub(crate) loader_dependencies: HashMap, AssetHash>, + pub(crate) labeled_assets: HashMap, LabeledAsset>, } impl From> for ErasedLoadedAsset { @@ -198,6 +214,7 @@ impl From> for ErasedLoadedAsset { value: Box::new(asset.value), dependencies: asset.dependencies, loader_dependencies: asset.loader_dependencies, + labeled_assets: asset.labeled_assets, } } } @@ -224,6 +241,19 @@ impl ErasedLoadedAsset { self.value.asset_type_name() } + /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists. + pub fn get_labeled( + &self, + label: impl Into>, + ) -> Option<&ErasedLoadedAsset> { + self.labeled_assets.get(&label.into()).map(|a| &a.asset) + } + + /// Iterate over all labels for "labeled assets" in the loaded asset + pub fn iter_labels(&self) -> impl Iterator { + self.labeled_assets.keys().map(|s| &**s) + } + /// Cast this loaded asset as the given type. If the type does not match, /// the original type-erased asset is returned. pub fn downcast(mut self) -> Result, ErasedLoadedAsset> { @@ -232,6 +262,7 @@ impl ErasedLoadedAsset { value: *value, dependencies: self.dependencies, loader_dependencies: self.loader_dependencies, + labeled_assets: self.labeled_assets, }), Err(value) => { self.value = value; @@ -259,100 +290,6 @@ impl AssetContainer for A { } } -/// A loaded asset and all its loaded subassets. -pub struct CompleteLoadedAsset { - /// The loaded asset. - pub(crate) asset: LoadedAsset, - /// The subassets by their label. - pub(crate) labeled_assets: HashMap, LabeledAsset>, -} - -impl CompleteLoadedAsset { - /// Take ownership of the stored [`Asset`] value. - pub fn take(self) -> A { - self.asset.value - } - - /// Returns the stored asset. - pub fn get_asset(&self) -> &LoadedAsset { - &self.asset - } - - /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists. - pub fn get_labeled( - &self, - label: impl Into>, - ) -> Option<&ErasedLoadedAsset> { - self.labeled_assets.get(&label.into()).map(|a| &a.asset) - } - - /// Iterate over all labels for "labeled assets" in the loaded asset - pub fn iter_labels(&self) -> impl Iterator { - self.labeled_assets.keys().map(|s| &**s) - } -} - -/// A "type erased / boxed" counterpart to [`CompleteLoadedAsset`]. This is used in places where the -/// loaded type is not statically known. -pub struct CompleteErasedLoadedAsset { - /// The loaded asset. - pub(crate) asset: ErasedLoadedAsset, - /// The subassets by their label. - pub(crate) labeled_assets: HashMap, LabeledAsset>, -} - -impl CompleteErasedLoadedAsset { - /// Cast (and take ownership) of the [`Asset`] value of the given type. This will return - /// [`Some`] if the stored type matches `A` and [`None`] if it does not. - pub fn take(self) -> Option { - self.asset.take() - } - - /// Returns the stored asset. - pub fn get_asset(&self) -> &ErasedLoadedAsset { - &self.asset - } - - /// Returns the [`ErasedLoadedAsset`] for the given label, if it exists. - pub fn get_labeled( - &self, - label: impl Into>, - ) -> Option<&ErasedLoadedAsset> { - self.labeled_assets.get(&label.into()).map(|a| &a.asset) - } - - /// Iterate over all labels for "labeled assets" in the loaded asset - pub fn iter_labels(&self) -> impl Iterator { - self.labeled_assets.keys().map(|s| &**s) - } - - /// Cast this loaded asset as the given type. If the type does not match, - /// the original type-erased asset is returned. - pub fn downcast( - mut self, - ) -> Result, CompleteErasedLoadedAsset> { - match self.asset.downcast::() { - Ok(asset) => Ok(CompleteLoadedAsset { - asset, - labeled_assets: self.labeled_assets, - }), - Err(asset) => { - self.asset = asset; - Err(self) - } - } - } -} - -impl From> for CompleteErasedLoadedAsset { - fn from(value: CompleteLoadedAsset) -> Self { - Self { - asset: value.asset.into(), - labeled_assets: value.labeled_assets, - } - } -} - /// An error that occurs when attempting to call [`NestedLoader::load`] which /// is configured to work [immediately]. /// @@ -461,11 +398,11 @@ impl<'a> LoadContext<'a> { &mut self, label: String, load: impl FnOnce(&mut LoadContext) -> A, - ) -> Result, DuplicateLabelAssetError> { + ) -> Handle { let mut context = self.begin_labeled_asset(); let asset = load(&mut context); - let complete_asset = context.finish(asset); - self.add_loaded_labeled_asset(label, complete_asset) + let loaded_asset = context.finish(asset); + self.add_loaded_labeled_asset(label, loaded_asset) } /// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label. @@ -478,11 +415,7 @@ impl<'a> LoadContext<'a> { /// new [`LoadContext`] to track the dependencies for the labeled asset. /// /// See [`AssetPath`] for more on labeled assets. - pub fn add_labeled_asset( - &mut self, - label: String, - asset: A, - ) -> Result, DuplicateLabelAssetError> { + pub fn add_labeled_asset(&mut self, label: String, asset: A) -> Handle { self.labeled_asset_scope(label, |_| asset) } @@ -494,37 +427,22 @@ impl<'a> LoadContext<'a> { pub fn add_loaded_labeled_asset( &mut self, label: impl Into>, - loaded_asset: CompleteLoadedAsset, - ) -> Result, DuplicateLabelAssetError> { + loaded_asset: LoadedAsset, + ) -> Handle { let label = label.into(); - let CompleteLoadedAsset { - asset, - labeled_assets, - } = loaded_asset; - let loaded_asset: ErasedLoadedAsset = asset.into(); + let loaded_asset: ErasedLoadedAsset = loaded_asset.into(); let labeled_path = self.asset_path.clone().with_label(label.clone()); let handle = self .asset_server .get_or_create_path_handle(labeled_path, None); - let has_duplicate = self - .labeled_assets - .insert( - label.clone(), - LabeledAsset { - asset: loaded_asset, - handle: handle.clone().untyped(), - }, - ) - .is_some(); - if has_duplicate { - return Err(DuplicateLabelAssetError(label.to_string())); - } - for (label, asset) in labeled_assets { - if self.labeled_assets.insert(label.clone(), asset).is_some() { - return Err(DuplicateLabelAssetError(label.to_string())); - } - } - Ok(handle) + self.labeled_assets.insert( + label, + LabeledAsset { + asset: loaded_asset, + handle: handle.clone().untyped(), + }, + ); + handle } /// Returns `true` if an asset with the label `label` exists in this context. @@ -536,13 +454,11 @@ impl<'a> LoadContext<'a> { } /// "Finishes" this context by populating the final [`Asset`] value. - pub fn finish(self, value: A) -> CompleteLoadedAsset { - CompleteLoadedAsset { - asset: LoadedAsset { - value, - dependencies: self.dependencies, - loader_dependencies: self.loader_dependencies, - }, + pub fn finish(self, value: A) -> LoadedAsset { + LoadedAsset { + value, + dependencies: self.dependencies, + loader_dependencies: self.loader_dependencies, labeled_assets: self.labeled_assets, } } @@ -613,8 +529,8 @@ impl<'a> LoadContext<'a> { meta: &dyn AssetMetaDyn, loader: &dyn ErasedAssetLoader, reader: &mut dyn Reader, - ) -> Result { - let complete_asset = self + ) -> Result { + let loaded_asset = self .asset_server .load_with_meta_loader_and_reader( &path, @@ -632,7 +548,7 @@ impl<'a> LoadContext<'a> { let info = meta.processed_info().as_ref(); let hash = info.map(|i| i.full_hash).unwrap_or_default(); self.loader_dependencies.insert(path, hash); - Ok(complete_asset) + Ok(loaded_asset) } /// Create a builder for loading nested assets in this context. @@ -674,8 +590,3 @@ pub enum ReadAssetBytesError { #[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, } - -/// An error when labeled assets have the same label, containing the duplicate label. -#[derive(Error, Debug)] -#[error("Encountered a duplicate label while loading an asset: \"{0}\"")] -pub struct DuplicateLabelAssetError(pub String); diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 1867e5b03c..13bea2b71d 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -4,8 +4,8 @@ use crate::{ io::Reader, meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings}, - Asset, AssetLoadError, AssetPath, CompleteErasedLoadedAsset, CompleteLoadedAsset, - ErasedAssetLoader, Handle, LoadContext, LoadDirectError, LoadedUntypedAsset, UntypedHandle, + Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext, + LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, }; use alloc::{borrow::ToOwned, boxed::Box, sync::Arc}; use core::any::TypeId; @@ -57,11 +57,11 @@ impl ReaderRef<'_> { /// If you know the type ID of the asset at runtime, but not at compile time, /// use [`with_dynamic_type`] followed by [`load`] to start loading an asset /// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]), -/// or a [`CompleteErasedLoadedAsset`] (via [`Immediate`]). +/// or a [`ErasedLoadedAsset`] (via [`Immediate`]). /// /// - in [`UnknownTyped`]: loading either a type-erased version of the asset -/// ([`CompleteErasedLoadedAsset`]), or a handle *to a handle* of the actual -/// asset ([`LoadedUntypedAsset`]). +/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset +/// ([`LoadedUntypedAsset`]). /// /// If you have no idea what type of asset you will be loading (not even at /// runtime with a [`TypeId`]), use this. @@ -305,9 +305,12 @@ impl NestedLoader<'_, '_, StaticTyped, Deferred> { pub fn load<'c, A: Asset>(self, path: impl Into>) -> Handle { let path = path.into().to_owned(); let handle = if self.load_context.should_load_dependencies { - self.load_context - .asset_server - .load_with_meta_transform(path, self.meta_transform, ()) + self.load_context.asset_server.load_with_meta_transform( + path, + self.meta_transform, + (), + true, + ) } else { self.load_context .asset_server @@ -386,7 +389,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> self, path: &AssetPath<'static>, asset_type_id: Option, - ) -> Result<(Arc, CompleteErasedLoadedAsset), LoadDirectError> { + ) -> Result<(Arc, ErasedLoadedAsset), LoadDirectError> { if path.label().is_some() { return Err(LoadDirectError::RequestedSubasset(path.clone())); } @@ -451,7 +454,7 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> { pub async fn load<'p, A: Asset>( self, path: impl Into>, - ) -> Result, LoadDirectError> { + ) -> Result, LoadDirectError> { let path = path.into().into_owned(); self.load_internal(&path, Some(TypeId::of::())) .await @@ -481,7 +484,7 @@ impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> { pub async fn load<'p>( self, path: impl Into>, - ) -> Result { + ) -> Result { let path = path.into().into_owned(); let asset_type_id = Some(self.typing.asset_type_id); self.load_internal(&path, asset_type_id) @@ -497,7 +500,7 @@ impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> { pub async fn load<'p>( self, path: impl Into>, - ) -> Result { + ) -> Result { let path = path.into().into_owned(); self.load_internal(&path, None) .await diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index 457f2479e4..3f780e3fb7 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -223,6 +223,16 @@ impl<'a> AssetPath<'a> { Ok((source, path, label)) } + /// Creates a new [`AssetPath`] from a [`PathBuf`]. + #[inline] + pub fn from_path_buf(path_buf: PathBuf) -> AssetPath<'a> { + AssetPath { + path: CowArc::Owned(path_buf.into()), + source: AssetSourceId::Default, + label: None, + } + } + /// Creates a new [`AssetPath`] from a [`Path`]. #[inline] pub fn from_path(path: &'a Path) -> AssetPath<'a> { @@ -478,6 +488,51 @@ impl<'a> AssetPath<'a> { } }) } + + /// Returns `true` if this [`AssetPath`] points to a file that is + /// outside of its [`AssetSource`](crate::io::AssetSource) folder. + /// + /// ## Example + /// ``` + /// # use bevy_asset::AssetPath; + /// // Inside the default AssetSource. + /// let path = AssetPath::parse("thingy.png"); + /// assert!( ! path.is_unapproved()); + /// let path = AssetPath::parse("gui/thingy.png"); + /// assert!( ! path.is_unapproved()); + /// + /// // Inside a different AssetSource. + /// let path = AssetPath::parse("embedded://thingy.png"); + /// assert!( ! path.is_unapproved()); + /// + /// // Exits the `AssetSource`s directory. + /// let path = AssetPath::parse("../thingy.png"); + /// assert!(path.is_unapproved()); + /// let path = AssetPath::parse("folder/../../thingy.png"); + /// assert!(path.is_unapproved()); + /// + /// // This references the linux root directory. + /// let path = AssetPath::parse("/home/thingy.png"); + /// assert!(path.is_unapproved()); + /// ``` + pub fn is_unapproved(&self) -> bool { + use std::path::Component; + let mut simplified = PathBuf::new(); + for component in self.path.components() { + match component { + Component::Prefix(_) | Component::RootDir => return true, + Component::CurDir => {} + Component::ParentDir => { + if !simplified.pop() { + return true; + } + } + Component::Normal(os_str) => simplified.push(os_str), + } + } + + false + } } impl AssetPath<'static> { diff --git a/crates/bevy_asset/src/processor/log.rs b/crates/bevy_asset/src/processor/log.rs index aefc3a96c6..f4a0f81862 100644 --- a/crates/bevy_asset/src/processor/log.rs +++ b/crates/bevy_asset/src/processor/log.rs @@ -5,7 +5,7 @@ use alloc::{ vec::Vec, }; use async_fs::File; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use futures_lite::{AsyncReadExt, AsyncWriteExt}; use std::path::PathBuf; use thiserror::Error; diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 24296e09a3..a239d66a9b 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -54,11 +54,11 @@ use crate::{ AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal, }, AssetLoadError, AssetMetaCheck, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError, - MissingAssetLoaderForExtensionError, WriteDefaultMetaError, + MissingAssetLoaderForExtensionError, UnapprovedPathMode, WriteDefaultMetaError, }; use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec}; use bevy_ecs::prelude::*; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_tasks::IoTaskPool; use futures_io::ErrorKind; use futures_lite::{AsyncReadExt, AsyncWriteExt, StreamExt}; @@ -122,6 +122,7 @@ impl AssetProcessor { AssetServerMode::Processed, AssetMetaCheck::Always, false, + UnapprovedPathMode::default(), ); Self { server, data } } diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index d2202d2334..b37265d0fb 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -7,7 +7,7 @@ use crate::{ processor::AssetProcessor, saver::{AssetSaver, SavedAsset}, transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset}, - AssetLoadError, AssetLoader, AssetPath, CompleteErasedLoadedAsset, DeserializeMetaError, + AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError, }; use alloc::{ @@ -305,15 +305,15 @@ impl<'a> ProcessContext<'a> { pub async fn load_source_asset( &mut self, meta: AssetMeta, - ) -> Result { + ) -> Result { let server = &self.processor.server; let loader_name = core::any::type_name::(); let loader = server.get_asset_loader_with_type_name(loader_name).await?; let mut reader = SliceReader::new(self.asset_bytes); - let complete_asset = server + let loaded_asset = server .load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true) .await?; - for (path, full_hash) in &complete_asset.asset.loader_dependencies { + for (path, full_hash) in &loaded_asset.loader_dependencies { self.new_processed_info .process_dependencies .push(ProcessDependencyInfo { @@ -321,7 +321,7 @@ impl<'a> ProcessContext<'a> { path: path.to_owned(), }); } - Ok(complete_asset) + Ok(loaded_asset) } /// The path of the asset being processed. diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 3b187e96ef..c8b96012ee 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -1,10 +1,10 @@ use crate::{ io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader, - CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle, + ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle, }; use alloc::boxed::Box; use atomicow::CowArc; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use core::{borrow::Borrow, hash::Hash, ops::Deref}; use serde::{Deserialize, Serialize}; @@ -44,7 +44,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static { fn save<'a>( &'a self, writer: &'a mut Writer, - complete_asset: &'a CompleteErasedLoadedAsset, + asset: &'a ErasedLoadedAsset, settings: &'a dyn Settings, ) -> BoxedFuture<'a, Result<(), Box>>; @@ -56,14 +56,14 @@ impl ErasedAssetSaver for S { fn save<'a>( &'a self, writer: &'a mut Writer, - complete_asset: &'a CompleteErasedLoadedAsset, + asset: &'a ErasedLoadedAsset, settings: &'a dyn Settings, ) -> BoxedFuture<'a, Result<(), Box>> { Box::pin(async move { let settings = settings .downcast_ref::() .expect("AssetLoader settings should match the loader type"); - let saved_asset = SavedAsset::::from_loaded(complete_asset).unwrap(); + let saved_asset = SavedAsset::::from_loaded(asset).unwrap(); if let Err(err) = self.save(writer, saved_asset, settings).await { return Err(err.into()); } @@ -91,11 +91,11 @@ impl<'a, A: Asset> Deref for SavedAsset<'a, A> { impl<'a, A: Asset> SavedAsset<'a, A> { /// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`. - pub fn from_loaded(complete_asset: &'a CompleteErasedLoadedAsset) -> Option { - let value = complete_asset.asset.value.downcast_ref::()?; + pub fn from_loaded(asset: &'a ErasedLoadedAsset) -> Option { + let value = asset.value.downcast_ref::()?; Some(SavedAsset { value, - labeled_assets: &complete_asset.labeled_assets, + labeled_assets: &asset.labeled_assets, }) } @@ -114,13 +114,17 @@ impl<'a, A: Asset> SavedAsset<'a, A> { } /// Returns the labeled asset, if it exists and matches this type. - pub fn get_labeled(&self, label: &Q) -> Option<&B> + pub fn get_labeled(&self, label: &Q) -> Option> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, { let labeled = self.labeled_assets.get(label)?; - labeled.asset.value.downcast_ref::() + let value = labeled.asset.value.downcast_ref::()?; + Some(SavedAsset { + value, + labeled_assets: &labeled.asset.labeled_assets, + }) } /// Returns the type-erased labeled asset, if it exists and matches this type. diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index e519bf86ae..1b3bb3cb65 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -11,7 +11,7 @@ use alloc::{ vec::Vec, }; use bevy_ecs::world::World; -use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet}; +use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_tasks::Task; use bevy_utils::TypeIdMap; use core::{any::TypeId, task::Waker}; @@ -347,14 +347,9 @@ impl AssetInfos { /// Returns `true` if the asset this path points to is still alive pub(crate) fn is_path_alive<'a>(&self, path: impl Into>) -> bool { - let path = path.into(); - - let result = self - .get_path_ids(&path) + self.get_path_ids(&path.into()) .filter_map(|id| self.infos.get(&id)) - .any(|info| info.weak_handle.strong_count() > 0); - - result + .any(|info| info.weak_handle.strong_count() > 0) } /// Returns `true` if the asset at this path should be reloaded diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 1250ff666a..08384e9efe 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -4,7 +4,7 @@ use crate::{ }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use async_broadcast::RecvError; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_tasks::IoTaskPool; use bevy_utils::TypeIdMap; use core::any::TypeId; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index af042f5710..ff5800474d 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -15,7 +15,7 @@ use crate::{ }, path::AssetPath, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets, - CompleteErasedLoadedAsset, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, + DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode, UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle, }; use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec}; @@ -26,7 +26,7 @@ use alloc::{ }; use atomicow::CowArc; use bevy_ecs::prelude::*; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_tasks::IoTaskPool; use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll}; use crossbeam_channel::{Receiver, Sender}; @@ -68,6 +68,7 @@ pub(crate) struct AssetServerData { sources: AssetSources, mode: AssetServerMode, meta_check: AssetMetaCheck, + unapproved_path_mode: UnapprovedPathMode, } /// The "asset mode" the server is currently in. @@ -82,13 +83,19 @@ pub enum AssetServerMode { impl AssetServer { /// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`](crate::io::AssetReader) storage will watch for changes to /// asset sources and hot-reload them. - pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self { + pub fn new( + sources: AssetSources, + mode: AssetServerMode, + watching_for_changes: bool, + unapproved_path_mode: UnapprovedPathMode, + ) -> Self { Self::new_with_loaders( sources, Default::default(), mode, AssetMetaCheck::Always, watching_for_changes, + unapproved_path_mode, ) } @@ -99,6 +106,7 @@ impl AssetServer { mode: AssetServerMode, meta_check: AssetMetaCheck, watching_for_changes: bool, + unapproved_path_mode: UnapprovedPathMode, ) -> Self { Self::new_with_loaders( sources, @@ -106,6 +114,7 @@ impl AssetServer { mode, meta_check, watching_for_changes, + unapproved_path_mode, ) } @@ -115,6 +124,7 @@ impl AssetServer { mode: AssetServerMode, meta_check: AssetMetaCheck, watching_for_changes: bool, + unapproved_path_mode: UnapprovedPathMode, ) -> Self { let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded(); let mut infos = AssetInfos::default(); @@ -128,6 +138,7 @@ impl AssetServer { asset_event_receiver, loaders, infos: RwLock::new(infos), + unapproved_path_mode, }), } } @@ -311,7 +322,16 @@ impl AssetServer { /// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load<'a, A: Asset>(&self, path: impl Into>) -> Handle { - self.load_with_meta_transform(path, None, ()) + self.load_with_meta_transform(path, None, (), false) + } + + /// Same as [`load`](AssetServer::load), but you can load assets from unaproved paths + /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode) + /// is [`Deny`](UnapprovedPathMode::Deny). + /// + /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + pub fn load_override<'a, A: Asset>(&self, path: impl Into>) -> Handle { + self.load_with_meta_transform(path, None, (), true) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -335,7 +355,20 @@ impl AssetServer { path: impl Into>, guard: G, ) -> Handle { - self.load_with_meta_transform(path, None, guard) + self.load_with_meta_transform(path, None, guard, false) + } + + /// Same as [`load`](AssetServer::load_acquire), but you can load assets from unaproved paths + /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode) + /// is [`Deny`](UnapprovedPathMode::Deny). + /// + /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>( + &self, + path: impl Into>, + guard: G, + ) -> Handle { + self.load_with_meta_transform(path, None, guard, true) } /// Begins loading an [`Asset`] of type `A` stored at `path`. The given `settings` function will override the asset's @@ -347,7 +380,30 @@ impl AssetServer { path: impl Into>, settings: impl Fn(&mut S) + Send + Sync + 'static, ) -> Handle { - self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), ()) + self.load_with_meta_transform( + path, + Some(loader_settings_meta_transform(settings)), + (), + false, + ) + } + + /// Same as [`load`](AssetServer::load_with_settings), but you can load assets from unaproved paths + /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode) + /// is [`Deny`](UnapprovedPathMode::Deny). + /// + /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + pub fn load_with_settings_override<'a, A: Asset, S: Settings>( + &self, + path: impl Into>, + settings: impl Fn(&mut S) + Send + Sync + 'static, + ) -> Handle { + self.load_with_meta_transform( + path, + Some(loader_settings_meta_transform(settings)), + (), + true, + ) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -366,7 +422,36 @@ impl AssetServer { settings: impl Fn(&mut S) + Send + Sync + 'static, guard: G, ) -> Handle { - self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), guard) + self.load_with_meta_transform( + path, + Some(loader_settings_meta_transform(settings)), + guard, + false, + ) + } + + /// Same as [`load`](AssetServer::load_acquire_with_settings), but you can load assets from unaproved paths + /// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode) + /// is [`Deny`](UnapprovedPathMode::Deny). + /// + /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + pub fn load_acquire_with_settings_override< + 'a, + A: Asset, + S: Settings, + G: Send + Sync + 'static, + >( + &self, + path: impl Into>, + settings: impl Fn(&mut S) + Send + Sync + 'static, + guard: G, + ) -> Handle { + self.load_with_meta_transform( + path, + Some(loader_settings_meta_transform(settings)), + guard, + true, + ) } pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>( @@ -374,8 +459,20 @@ impl AssetServer { path: impl Into>, meta_transform: Option, guard: G, + override_unapproved: bool, ) -> Handle { let path = path.into().into_owned(); + + if path.is_unapproved() { + match (&self.data.unapproved_path_mode, override_unapproved) { + (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {} + (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => { + error!("Asset path {path} is unapproved. See UnapprovedPathMode for details."); + return Handle::default(); + } + } + } + let mut infos = self.data.infos.write(); let (handle, should_load) = infos.get_or_create_path_handle::( path.clone(), @@ -699,18 +796,12 @@ impl AssetServer { /// Sends a load event for the given `loaded_asset` and does the same recursively for all /// labeled assets. - fn send_loaded_asset(&self, id: UntypedAssetId, mut complete_asset: CompleteErasedLoadedAsset) { - for (_, labeled_asset) in complete_asset.labeled_assets.drain() { - self.send_asset_event(InternalAssetEvent::Loaded { - id: labeled_asset.handle.id(), - loaded_asset: labeled_asset.asset, - }); + fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) { + for (_, labeled_asset) in loaded_asset.labeled_assets.drain() { + self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset); } - self.send_asset_event(InternalAssetEvent::Loaded { - id, - loaded_asset: complete_asset.asset, - }); + self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset }); } /// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded. @@ -1334,7 +1425,7 @@ impl AssetServer { reader: &mut dyn Reader, load_dependencies: bool, populate_hashes: bool, - ) -> Result { + ) -> Result { // TODO: experiment with this let asset_path = asset_path.clone_owned(); let load_context = diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index d2573582f1..802e3aeaa7 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -1,10 +1,7 @@ -use crate::{ - meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, - UntypedHandle, -}; +use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; use alloc::boxed::Box; use atomicow::CowArc; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_tasks::ConditionalSendFuture; use core::{ borrow::Borrow, @@ -59,11 +56,11 @@ impl DerefMut for TransformedAsset { impl TransformedAsset { /// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`. - pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option { - if let Ok(value) = complete_asset.asset.value.downcast::() { + pub fn from_loaded(asset: ErasedLoadedAsset) -> Option { + if let Ok(value) = asset.value.downcast::() { return Some(TransformedAsset { value: *value, - labeled_assets: complete_asset.labeled_assets, + labeled_assets: asset.labeled_assets, }); } None @@ -90,13 +87,117 @@ impl TransformedAsset { &mut self.value } /// Returns the labeled asset, if it exists and matches this type. - pub fn get_labeled(&mut self, label: &'_ Q) -> Option<&mut B> + pub fn get_labeled(&mut self, label: &Q) -> Option> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, { let labeled = self.labeled_assets.get_mut(label)?; - labeled.asset.value.downcast_mut::() + let value = labeled.asset.value.downcast_mut::()?; + Some(TransformedSubAsset { + value, + labeled_assets: &mut labeled.asset.labeled_assets, + }) + } + /// Returns the type-erased labeled asset, if it exists and matches this type. + pub fn get_erased_labeled(&self, label: &Q) -> Option<&ErasedLoadedAsset> + where + CowArc<'static, str>: Borrow, + Q: ?Sized + Hash + Eq, + { + let labeled = self.labeled_assets.get(label)?; + Some(&labeled.asset) + } + /// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists. + pub fn get_untyped_handle(&self, label: &Q) -> Option + where + CowArc<'static, str>: Borrow, + Q: ?Sized + Hash + Eq, + { + let labeled = self.labeled_assets.get(label)?; + Some(labeled.handle.clone()) + } + /// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B` + pub fn get_handle(&self, label: &Q) -> Option> + where + CowArc<'static, str>: Borrow, + Q: ?Sized + Hash + Eq, + { + let labeled = self.labeled_assets.get(label)?; + if let Ok(handle) = labeled.handle.clone().try_typed::() { + return Some(handle); + } + None + } + /// Adds `asset` as a labeled sub asset using `label` and `handle` + pub fn insert_labeled( + &mut self, + label: impl Into>, + handle: impl Into, + asset: impl Into, + ) { + let labeled = LabeledAsset { + asset: asset.into(), + handle: handle.into(), + }; + self.labeled_assets.insert(label.into(), labeled); + } + /// Iterate over all labels for "labeled assets" in the loaded asset + pub fn iter_labels(&self) -> impl Iterator { + self.labeled_assets.keys().map(|s| &**s) + } +} + +/// A labeled sub-asset of [`TransformedAsset`] +pub struct TransformedSubAsset<'a, A: Asset> { + value: &'a mut A, + labeled_assets: &'a mut HashMap, LabeledAsset>, +} + +impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> { + type Target = A; + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value + } +} + +impl<'a, A: Asset> TransformedSubAsset<'a, A> { + /// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`. + pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option { + let value = asset.value.downcast_mut::()?; + Some(TransformedSubAsset { + value, + labeled_assets: &mut asset.labeled_assets, + }) + } + /// Retrieves the value of this asset. + #[inline] + pub fn get(&self) -> &A { + self.value + } + /// Mutably retrieves the value of this asset. + #[inline] + pub fn get_mut(&mut self) -> &mut A { + self.value + } + /// Returns the labeled asset, if it exists and matches this type. + pub fn get_labeled(&mut self, label: &Q) -> Option> + where + CowArc<'static, str>: Borrow, + Q: ?Sized + Hash + Eq, + { + let labeled = self.labeled_assets.get_mut(label)?; + let value = labeled.asset.value.downcast_mut::()?; + Some(TransformedSubAsset { + value, + labeled_assets: &mut labeled.asset.labeled_assets, + }) } /// Returns the type-erased labeled asset, if it exists and matches this type. pub fn get_erased_labeled(&self, label: &Q) -> Option<&ErasedLoadedAsset> diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 349cf6b6a4..8a4a406acc 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -57,6 +57,16 @@ pub struct PlaybackSettings { /// Optional scale factor applied to the positions of this audio source and the listener, /// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale). pub spatial_scale: Option, + /// The point in time in the audio clip where playback should start. If set to `None`, it will + /// play from the beginning of the clip. + /// + /// If the playback mode is set to `Loop`, each loop will start from this position. + pub start_position: Option, + /// How long the audio should play before stopping. If set, the clip will play for at most + /// the specified duration. If set to `None`, it will play for as long as it can. + /// + /// If the playback mode is set to `Loop`, each loop will last for this duration. + pub duration: Option, } impl Default for PlaybackSettings { @@ -81,6 +91,8 @@ impl PlaybackSettings { muted: false, spatial: false, spatial_scale: None, + start_position: None, + duration: None, }; /// Will play the associated audio source in a loop. @@ -136,6 +148,18 @@ impl PlaybackSettings { self.spatial_scale = Some(spatial_scale); self } + + /// Helper to use a custom playback start position. + pub const fn with_start_position(mut self, start_position: core::time::Duration) -> Self { + self.start_position = Some(start_position); + self + } + + /// Helper to use a custom playback duration. + pub const fn with_duration(mut self, duration: core::time::Duration) -> Self { + self.duration = Some(duration); + self + } } /// Settings for the listener for spatial audio sources. diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index 1869fb4755..9fc757af44 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -156,12 +156,49 @@ pub(crate) fn play_queued_audio_system( } }; + let decoder = audio_source.decoder(); + match settings.mode { - PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()), + PlaybackMode::Loop => match (settings.start_position, settings.duration) { + // custom start position and duration + (Some(start_position), Some(duration)) => sink.append( + decoder + .skip_duration(start_position) + .take_duration(duration) + .repeat_infinite(), + ), + + // custom start position + (Some(start_position), None) => { + sink.append(decoder.skip_duration(start_position).repeat_infinite()); + } + + // custom duration + (None, Some(duration)) => { + sink.append(decoder.take_duration(duration).repeat_infinite()); + } + + // full clip + (None, None) => sink.append(decoder.repeat_infinite()), + }, PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => { - sink.append(audio_source.decoder()); + match (settings.start_position, settings.duration) { + (Some(start_position), Some(duration)) => sink.append( + decoder + .skip_duration(start_position) + .take_duration(duration), + ), + + (Some(start_position), None) => { + sink.append(decoder.skip_duration(start_position)); + } + + (None, Some(duration)) => sink.append(decoder.take_duration(duration)), + + (None, None) => sink.append(decoder), + } } - }; + } let mut sink = SpatialAudioSink::new(sink); @@ -196,12 +233,49 @@ pub(crate) fn play_queued_audio_system( } }; + let decoder = audio_source.decoder(); + match settings.mode { - PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()), + PlaybackMode::Loop => match (settings.start_position, settings.duration) { + // custom start position and duration + (Some(start_position), Some(duration)) => sink.append( + decoder + .skip_duration(start_position) + .take_duration(duration) + .repeat_infinite(), + ), + + // custom start position + (Some(start_position), None) => { + sink.append(decoder.skip_duration(start_position).repeat_infinite()); + } + + // custom duration + (None, Some(duration)) => { + sink.append(decoder.take_duration(duration).repeat_infinite()); + } + + // full clip + (None, None) => sink.append(decoder.repeat_infinite()), + }, PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => { - sink.append(audio_source.decoder()); + match (settings.start_position, settings.duration) { + (Some(start_position), Some(duration)) => sink.append( + decoder + .skip_duration(start_position) + .take_duration(duration), + ), + + (Some(start_position), None) => { + sink.append(decoder.skip_duration(start_position)); + } + + (None, Some(duration)) => sink.append(decoder.take_duration(duration)), + + (None, None) => sink.append(decoder), + } } - }; + } let mut sink = AudioSink::new(sink); diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index babae2f8a9..becbf5d1da 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -58,13 +58,13 @@ pub use sinks::*; use bevy_app::prelude::*; use bevy_asset::{Asset, AssetApp}; use bevy_ecs::prelude::*; -use bevy_transform::TransformSystem; +use bevy_transform::TransformSystems; use audio_output::*; /// Set for the audio playback systems, so they can share a run condition #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] -struct AudioPlaySet; +struct AudioPlaybackSystems; /// Adds support for audio playback to a Bevy Application /// @@ -90,13 +90,13 @@ impl Plugin for AudioPlugin { .insert_resource(DefaultSpatialScale(self.default_spatial_scale)) .configure_sets( PostUpdate, - AudioPlaySet + AudioPlaybackSystems .run_if(audio_output_available) - .after(TransformSystem::TransformPropagate), // For spatial audio transforms + .after(TransformSystems::Propagate), // For spatial audio transforms ) .add_systems( PostUpdate, - (update_emitter_positions, update_listener_positions).in_set(AudioPlaySet), + (update_emitter_positions, update_listener_positions).in_set(AudioPlaybackSystems), ) .init_resource::(); @@ -118,7 +118,8 @@ impl AddAudioSource for App { { self.init_asset::().add_systems( PostUpdate, - (play_queued_audio_system::, cleanup_finished_audio::).in_set(AudioPlaySet), + (play_queued_audio_system::, cleanup_finished_audio::) + .in_set(AudioPlaybackSystems), ); self } diff --git a/crates/bevy_audio/src/sinks.rs b/crates/bevy_audio/src/sinks.rs index b0c77456e1..1e020d1fd8 100644 --- a/crates/bevy_audio/src/sinks.rs +++ b/crates/bevy_audio/src/sinks.rs @@ -1,10 +1,11 @@ +use crate::Volume; use bevy_ecs::component::Component; use bevy_math::Vec3; use bevy_transform::prelude::Transform; +use core::time::Duration; +pub use rodio::source::SeekError; use rodio::{Sink, SpatialSink}; -use crate::Volume; - /// Common interactions with an audio sink. pub trait AudioSinkPlayback { /// Gets the volume of the sound as a [`Volume`]. @@ -41,6 +42,34 @@ pub trait AudioSinkPlayback { /// No effect if not paused. fn play(&self); + /// Returns the position of the sound that's being played. + /// + /// This takes into account any speedup or delay applied. + /// + /// Example: if you [`set_speed(2.0)`](Self::set_speed) and [`position()`](Self::position) returns *5s*, + /// then the position in the recording is *10s* from its start. + fn position(&self) -> Duration; + + /// Attempts to seek to a given position in the current source. + /// + /// This blocks between 0 and ~5 milliseconds. + /// + /// As long as the duration of the source is known, seek is guaranteed to saturate + /// at the end of the source. For example given a source that reports a total duration + /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to + /// 42 seconds. + /// + /// # Errors + /// This function will return [`SeekError::NotSupported`] if one of the underlying + /// sources does not support seeking. + /// + /// It will return an error if an implementation ran + /// into one during the seek. + /// + /// When seeking beyond the end of a source, this + /// function might return an error if the duration of the source is not known. + fn try_seek(&self, pos: Duration) -> Result<(), SeekError>; + /// Pauses playback of this sink. /// /// No effect if already paused. @@ -160,6 +189,14 @@ impl AudioSinkPlayback for AudioSink { self.sink.play(); } + fn position(&self) -> Duration { + self.sink.get_pos() + } + + fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { + self.sink.try_seek(pos) + } + fn pause(&self) { self.sink.pause(); } @@ -256,6 +293,14 @@ impl AudioSinkPlayback for SpatialAudioSink { self.sink.play(); } + fn position(&self) -> Duration { + self.sink.get_pos() + } + + fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { + self.sink.try_seek(pos) + } + fn pause(&self) { self.sink.pause(); } diff --git a/crates/bevy_audio/src/volume.rs b/crates/bevy_audio/src/volume.rs index b1378ae485..3c19d189ef 100644 --- a/crates/bevy_audio/src/volume.rs +++ b/crates/bevy_audio/src/volume.rs @@ -144,7 +144,7 @@ impl Volume { /// Returns the volume in decibels as a float. /// - /// If the volume is silent / off / muted, i.e. it's underlying linear scale + /// If the volume is silent / off / muted, i.e. its underlying linear scale /// is `0.0`, this method returns negative infinity. pub fn to_decibels(&self) -> f32 { match self { diff --git a/crates/bevy_color/src/color_ops.rs b/crates/bevy_color/src/color_ops.rs index 60a535d9fe..a3266ba2d6 100644 --- a/crates/bevy_color/src/color_ops.rs +++ b/crates/bevy_color/src/color_ops.rs @@ -60,7 +60,7 @@ pub trait Alpha: Sized { /// Return a new version of this color with the given alpha value. fn with_alpha(&self, alpha: f32) -> Self; - /// Return a the alpha component of this color. + /// Return the alpha component of this color. fn alpha(&self) -> f32; /// Sets the alpha component of this color. @@ -77,6 +77,20 @@ pub trait Alpha: Sized { } } +impl Alpha for f32 { + fn with_alpha(&self, alpha: f32) -> Self { + alpha + } + + fn alpha(&self) -> f32 { + *self + } + + fn set_alpha(&mut self, alpha: f32) { + *self = alpha; + } +} + /// Trait for manipulating the hue of a color. pub trait Hue: Sized { /// Return a new version of this color with the hue channel set to the given value. diff --git a/crates/bevy_color/src/interpolate.rs b/crates/bevy_color/src/interpolate.rs new file mode 100644 index 0000000000..75d1717d5a --- /dev/null +++ b/crates/bevy_color/src/interpolate.rs @@ -0,0 +1,37 @@ +//! TODO: Implement for non-linear colors. + +#[cfg(test)] +mod test { + use bevy_math::StableInterpolate; + + use crate::{Gray, Laba, LinearRgba, Oklaba, Srgba, Xyza}; + + #[test] + pub fn test_color_stable_interpolate() { + let b = Srgba::BLACK; + let w = Srgba::WHITE; + assert_eq!( + b.interpolate_stable(&w, 0.5), + Srgba::new(0.5, 0.5, 0.5, 1.0), + ); + + let b = LinearRgba::BLACK; + let w = LinearRgba::WHITE; + assert_eq!( + b.interpolate_stable(&w, 0.5), + LinearRgba::new(0.5, 0.5, 0.5, 1.0), + ); + + let b = Xyza::BLACK; + let w = Xyza::WHITE; + assert_eq!(b.interpolate_stable(&w, 0.5), Xyza::gray(0.5),); + + let b = Laba::BLACK; + let w = Laba::WHITE; + assert_eq!(b.interpolate_stable(&w, 0.5), Laba::new(0.5, 0.0, 0.0, 1.0),); + + let b = Oklaba::BLACK; + let w = Oklaba::WHITE; + assert_eq!(b.interpolate_stable(&w, 0.5), Oklaba::gray(0.5),); + } +} diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index e1ee1fbe38..712da5d7ec 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -105,6 +105,7 @@ mod color_range; mod hsla; mod hsva; mod hwba; +mod interpolate; mod laba; mod lcha; mod linear_rgba; @@ -265,6 +266,12 @@ macro_rules! impl_componentwise_vector_space { $($element: 0.0,)+ }; } + + impl bevy_math::StableInterpolate for $ty { + fn interpolate_stable(&self, other: &Self, t: f32) -> Self { + bevy_math::VectorSpace::lerp(*self, *other, t) + } + } }; } diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index ead2adf039..a811e8e313 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -177,8 +177,8 @@ impl Srgba { pub fn to_hex(&self) -> String { let [r, g, b, a] = self.to_u8_array(); match a { - 255 => format!("#{:02X}{:02X}{:02X}", r, g, b), - _ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a), + 255 => format!("#{r:02X}{g:02X}{b:02X}"), + _ => format!("#{r:02X}{g:02X}{b:02X}{a:02X}"), } } diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 1530a116ad..304c007104 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -13,12 +13,10 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -dds = ["bevy_render/dds", "bevy_image/dds"] trace = [] webgl = [] webgpu = [] tonemapping_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"] -smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"] [dependencies] # bevy @@ -35,7 +33,7 @@ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs b/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs index cc16cd4630..38d55bc9de 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_render::{ render_resource::{StorageBuffer, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, diff --git a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs index f94a61d09b..172de3c393 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs @@ -1,15 +1,15 @@ use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, AssetApp, Assets, Handle}; +use bevy_asset::{embedded_asset, AssetApp, Assets, Handle}; use bevy_ecs::prelude::*; use bevy_render::{ extract_component::ExtractComponentPlugin, render_asset::RenderAssetPlugin, render_graph::RenderGraphApp, render_resource::{ - Buffer, BufferDescriptor, BufferUsages, PipelineCache, Shader, SpecializedComputePipelines, + Buffer, BufferDescriptor, BufferUsages, PipelineCache, SpecializedComputePipelines, }, renderer::RenderDevice, - ExtractSchedule, Render, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSystems, }; mod buffers; @@ -21,9 +21,7 @@ mod settings; use buffers::{extract_buffers, prepare_buffers, AutoExposureBuffers}; pub use compensation_curve::{AutoExposureCompensationCurve, AutoExposureCompensationCurveError}; use node::AutoExposureNode; -use pipeline::{ - AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline, METERING_SHADER_HANDLE, -}; +use pipeline::{AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline}; pub use settings::AutoExposure; use crate::{ @@ -43,12 +41,7 @@ struct AutoExposureResources { impl Plugin for AutoExposurePlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - METERING_SHADER_HANDLE, - "auto_exposure.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "auto_exposure.wgsl"); app.add_plugins(RenderAssetPlugin::::default()) .register_type::() @@ -72,8 +65,8 @@ impl Plugin for AutoExposurePlugin { .add_systems( Render, ( - prepare_buffers.in_set(RenderSet::Prepare), - queue_view_auto_exposure_pipelines.in_set(RenderSet::Queue), + prepare_buffers.in_set(RenderSystems::Prepare), + queue_view_auto_exposure_pipelines.in_set(RenderSystems::Queue), ), ) .add_render_graph_node::(Core3d, node::AutoExposure) diff --git a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index 06fa118827..28ed6b4ee8 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -1,7 +1,7 @@ use super::compensation_curve::{ AutoExposureCompensationCurve, AutoExposureCompensationCurveUniform, }; -use bevy_asset::{prelude::*, weak_handle}; +use bevy_asset::{load_embedded_asset, prelude::*}; use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_render::{ @@ -44,9 +44,6 @@ pub enum AutoExposurePass { Average, } -pub const METERING_SHADER_HANDLE: Handle = - weak_handle!("05c84384-afa4-41d9-844e-e9cd5e7609af"); - pub const HISTOGRAM_BIN_COUNT: u64 = 64; impl FromWorld for AutoExposurePipeline { @@ -71,7 +68,7 @@ impl FromWorld for AutoExposurePipeline { ), ), ), - histogram_shader: METERING_SHADER_HANDLE.clone(), + histogram_shader: load_embedded_asset!(world, "auto_exposure.wgsl"), } } } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs index cf6fdd4e24..ae359a8a01 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs @@ -5,7 +5,7 @@ use bevy_asset::Handle; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_image::Image; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::extract_component::ExtractComponent; +use bevy_render::{extract_component::ExtractComponent, view::Hdr}; use bevy_utils::default; /// Component that enables auto exposure for an HDR-enabled 2d or 3d camera. @@ -25,6 +25,7 @@ use bevy_utils::default; /// **Auto Exposure requires compute shaders and is not compatible with WebGL2.** #[derive(Component, Clone, Reflect, ExtractComponent)] #[reflect(Component, Default, Clone)] +#[require(Hdr)] pub struct AutoExposure { /// The range of exposure values for the histogram. /// diff --git a/crates/bevy_core_pipeline/src/blit/mod.rs b/crates/bevy_core_pipeline/src/blit/mod.rs index 53c54c6d2d..111b6e443b 100644 --- a/crates/bevy_core_pipeline/src/blit/mod.rs +++ b/crates/bevy_core_pipeline/src/blit/mod.rs @@ -1,5 +1,5 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_ecs::prelude::*; use bevy_render::{ render_resource::{ @@ -12,14 +12,12 @@ use bevy_render::{ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; -pub const BLIT_SHADER_HANDLE: Handle = weak_handle!("59be3075-c34e-43e7-bf24-c8fe21a0192e"); - /// Adds support for specialized "blit pipelines", which can be used to write one texture to another. pub struct BlitPlugin; impl Plugin for BlitPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, BLIT_SHADER_HANDLE, "blit.wgsl", Shader::from_wgsl); + embedded_asset!(app, "blit.wgsl"); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.allow_ambiguous_resource::>(); @@ -40,6 +38,7 @@ impl Plugin for BlitPlugin { pub struct BlitPipeline { pub texture_bind_group: BindGroupLayout, pub sampler: Sampler, + pub shader: Handle, } impl FromWorld for BlitPipeline { @@ -62,6 +61,7 @@ impl FromWorld for BlitPipeline { BlitPipeline { texture_bind_group, sampler, + shader: load_embedded_asset!(render_world, "blit.wgsl"), } } } @@ -82,7 +82,7 @@ impl SpecializedRenderPipeline for BlitPipeline { layout: vec![self.texture_bind_group.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: BLIT_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![], entry_point: "fs_main".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index 544b420bfd..88da2db0cc 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -1,5 +1,6 @@ -use super::{Bloom, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT}; +use super::{Bloom, BLOOM_TEXTURE_FORMAT}; use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_ecs::{ prelude::{Component, Entity}, resource::Resource, @@ -26,6 +27,8 @@ pub struct BloomDownsamplingPipeline { /// Layout with a texture, a sampler, and uniforms pub bind_group_layout: BindGroupLayout, pub sampler: Sampler, + /// The shader asset handle. + pub shader: Handle, } #[derive(PartialEq, Eq, Hash, Clone)] @@ -78,6 +81,7 @@ impl FromWorld for BloomDownsamplingPipeline { BloomDownsamplingPipeline { bind_group_layout, sampler, + shader: load_embedded_asset!(world, "bloom.wgsl"), } } } @@ -120,7 +124,7 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline { layout, vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point, targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 295b04c6ad..10ffdf9c63 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -2,7 +2,6 @@ mod downsampling_pipeline; mod settings; mod upsampling_pipeline; -use bevy_color::{Gray, LinearRgba}; pub use settings::{Bloom, BloomCompositeMode, BloomPrefilter}; use crate::{ @@ -10,7 +9,8 @@ use crate::{ core_3d::graph::{Core3d, Node3d}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::embedded_asset; +use bevy_color::{Gray, LinearRgba}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_math::{ops, UVec2}; use bevy_render::{ @@ -24,25 +24,25 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, view::ViewTarget, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use downsampling_pipeline::{ prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds, BloomUniforms, }; +#[cfg(feature = "trace")] +use tracing::info_span; use upsampling_pipeline::{ prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds, }; -const BLOOM_SHADER_HANDLE: Handle = weak_handle!("c9190ddc-573b-4472-8b21-573cab502b73"); - const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Ufloat; pub struct BloomPlugin; impl Plugin for BloomPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl); + embedded_asset!(app, "bloom.wgsl"); app.register_type::(); app.register_type::(); @@ -61,10 +61,10 @@ impl Plugin for BloomPlugin { .add_systems( Render, ( - prepare_downsampling_pipeline.in_set(RenderSet::Prepare), - prepare_upsampling_pipeline.in_set(RenderSet::Prepare), - prepare_bloom_textures.in_set(RenderSet::PrepareResources), - prepare_bloom_bind_groups.in_set(RenderSet::PrepareBindGroups), + prepare_downsampling_pipeline.in_set(RenderSystems::Prepare), + prepare_upsampling_pipeline.in_set(RenderSystems::Prepare), + prepare_bloom_textures.in_set(RenderSystems::PrepareResources), + prepare_bloom_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) // Add bloom to the 3d render graph @@ -108,10 +108,10 @@ impl ViewNode for BloomNode { // Atypically for a post-processing effect, we do not need to // use a secondary texture normally provided by view_target.post_process_write(), // instead we write into our own bloom texture and then directly back onto main. - fn run( + fn run<'w>( &self, _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, + render_context: &mut RenderContext<'w>, ( camera, view_target, @@ -121,8 +121,8 @@ impl ViewNode for BloomNode { bloom_settings, upsampling_pipeline_ids, downsampling_pipeline_ids, - ): QueryItem, - world: &World, + ): QueryItem<'w, Self::ViewQuery>, + world: &'w World, ) -> Result<(), NodeRunError> { if bloom_settings.intensity == 0.0 { return Ok(()); @@ -149,133 +149,152 @@ impl ViewNode for BloomNode { return Ok(()); }; - render_context.command_encoder().push_debug_group("bloom"); - + let view_texture = view_target.main_texture_view(); + let view_texture_unsampled = view_target.get_unsampled_color_attachment(); let diagnostics = render_context.diagnostic_recorder(); - let command_encoder = render_context.command_encoder(); - let time_span = diagnostics.time_span(command_encoder, "bloom"); - // First downsample pass - { - let downsampling_first_bind_group = render_context.render_device().create_bind_group( - "bloom_downsampling_first_bind_group", - &downsampling_pipeline_res.bind_group_layout, - &BindGroupEntries::sequential(( - // Read from main texture directly - view_target.main_texture_view(), - &bind_groups.sampler, - uniforms.clone(), - )), - ); + render_context.add_command_buffer_generation_task(move |render_device| { + #[cfg(feature = "trace")] + let _bloom_span = info_span!("bloom").entered(); - let view = &bloom_texture.view(0); - let mut downsampling_first_pass = - render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("bloom_downsampling_first_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, + let mut command_encoder = + render_device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("bloom_command_encoder"), }); - downsampling_first_pass.set_render_pipeline(downsampling_first_pipeline); - downsampling_first_pass.set_bind_group( - 0, - &downsampling_first_bind_group, - &[uniform_index.index()], - ); - downsampling_first_pass.draw(0..3, 0..1); - } + command_encoder.push_debug_group("bloom"); + let time_span = diagnostics.time_span(&mut command_encoder, "bloom"); - // Other downsample passes - for mip in 1..bloom_texture.mip_count { - let view = &bloom_texture.view(mip); - let mut downsampling_pass = - render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("bloom_downsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - downsampling_pass.set_render_pipeline(downsampling_pipeline); - downsampling_pass.set_bind_group( - 0, - &bind_groups.downsampling_bind_groups[mip as usize - 1], - &[uniform_index.index()], - ); - downsampling_pass.draw(0..3, 0..1); - } + // First downsample pass + { + let downsampling_first_bind_group = render_device.create_bind_group( + "bloom_downsampling_first_bind_group", + &downsampling_pipeline_res.bind_group_layout, + &BindGroupEntries::sequential(( + // Read from main texture directly + view_texture, + &bind_groups.sampler, + uniforms.clone(), + )), + ); - // Upsample passes except the final one - for mip in (1..bloom_texture.mip_count).rev() { - let view = &bloom_texture.view(mip - 1); - let mut upsampling_pass = - render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("bloom_upsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - resolve_target: None, - ops: Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - upsampling_pass.set_render_pipeline(upsampling_pipeline); - upsampling_pass.set_bind_group( - 0, - &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize], - &[uniform_index.index()], - ); - let blend = compute_blend_factor( - bloom_settings, - mip as f32, - (bloom_texture.mip_count - 1) as f32, - ); - upsampling_pass.set_blend_constant(LinearRgba::gray(blend)); - upsampling_pass.draw(0..3, 0..1); - } - - // Final upsample pass - // This is very similar to the above upsampling passes with the only difference - // being the pipeline (which itself is barely different) and the color attachment - { - let mut upsampling_final_pass = - render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_target.get_unsampled_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline); - upsampling_final_pass.set_bind_group( - 0, - &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], - &[uniform_index.index()], - ); - if let Some(viewport) = camera.viewport.as_ref() { - upsampling_final_pass.set_camera_viewport(viewport); + let view = &bloom_texture.view(0); + let mut downsampling_first_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_downsampling_first_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + downsampling_first_pass.set_pipeline(downsampling_first_pipeline); + downsampling_first_pass.set_bind_group( + 0, + &downsampling_first_bind_group, + &[uniform_index.index()], + ); + downsampling_first_pass.draw(0..3, 0..1); } - let blend = - compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); - upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend)); - upsampling_final_pass.draw(0..3, 0..1); - } - time_span.end(render_context.command_encoder()); - render_context.command_encoder().pop_debug_group(); + // Other downsample passes + for mip in 1..bloom_texture.mip_count { + let view = &bloom_texture.view(mip); + let mut downsampling_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_downsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + downsampling_pass.set_pipeline(downsampling_pipeline); + downsampling_pass.set_bind_group( + 0, + &bind_groups.downsampling_bind_groups[mip as usize - 1], + &[uniform_index.index()], + ); + downsampling_pass.draw(0..3, 0..1); + } + + // Upsample passes except the final one + for mip in (1..bloom_texture.mip_count).rev() { + let view = &bloom_texture.view(mip - 1); + let mut upsampling_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_upsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + upsampling_pass.set_pipeline(upsampling_pipeline); + upsampling_pass.set_bind_group( + 0, + &bind_groups.upsampling_bind_groups + [(bloom_texture.mip_count - mip - 1) as usize], + &[uniform_index.index()], + ); + let blend = compute_blend_factor( + bloom_settings, + mip as f32, + (bloom_texture.mip_count - 1) as f32, + ); + upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into()); + upsampling_pass.draw(0..3, 0..1); + } + + // Final upsample pass + // This is very similar to the above upsampling passes with the only difference + // being the pipeline (which itself is barely different) and the color attachment + { + let mut upsampling_final_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_upsampling_final_pass"), + color_attachments: &[Some(view_texture_unsampled)], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + upsampling_final_pass.set_pipeline(upsampling_final_pipeline); + upsampling_final_pass.set_bind_group( + 0, + &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], + &[uniform_index.index()], + ); + if let Some(viewport) = camera.viewport.as_ref() { + upsampling_final_pass.set_viewport( + viewport.physical_position.x as f32, + viewport.physical_position.y as f32, + viewport.physical_size.x as f32, + viewport.physical_size.y as f32, + viewport.depth.start, + viewport.depth.end, + ); + } + let blend = + compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); + upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into()); + upsampling_final_pass.draw(0..3, 0..1); + } + + time_span.end(&mut command_encoder); + command_encoder.pop_debug_group(); + command_encoder.finish() + }); Ok(()) } diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index f6ee8dbd1e..195c2eb4c0 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -1,8 +1,12 @@ use super::downsampling_pipeline::BloomUniforms; -use bevy_ecs::{prelude::Component, query::QueryItem, reflect::ReflectComponent}; +use bevy_ecs::{ + prelude::Component, + query::{QueryItem, With}, + reflect::ReflectComponent, +}; use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; +use bevy_render::{extract_component::ExtractComponent, prelude::Camera, view::Hdr}; /// Applies a bloom effect to an HDR-enabled 2d or 3d camera. /// @@ -26,6 +30,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; /// used in Bevy as well as a visualization of the curve's respective scattering profile. #[derive(Component, Reflect, Clone)] #[reflect(Component, Default, Clone)] +#[require(Hdr)] pub struct Bloom { /// Controls the baseline of how much the image is scattered (default: 0.15). /// @@ -219,7 +224,7 @@ pub enum BloomCompositeMode { impl ExtractComponent for Bloom { type QueryData = (&'static Self, &'static Camera); - type QueryFilter = (); + type QueryFilter = With; type Out = (Self, BloomUniforms); fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option { @@ -228,9 +233,8 @@ impl ExtractComponent for Bloom { camera.physical_viewport_size(), camera.physical_target_size(), camera.is_active, - camera.hdr, ) { - (Some(URect { min: origin, .. }), Some(size), Some(target_size), true, true) + (Some(URect { min: origin, .. }), Some(size), Some(target_size), true) if size.x != 0 && size.y != 0 => { let threshold = bloom.prefilter.threshold; diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index e4c4ed4a64..f381e664a9 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -1,8 +1,8 @@ use super::{ - downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_SHADER_HANDLE, - BLOOM_TEXTURE_FORMAT, + downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_TEXTURE_FORMAT, }; use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_ecs::{ prelude::{Component, Entity}, resource::Resource, @@ -27,6 +27,8 @@ pub struct UpsamplingPipelineIds { #[derive(Resource)] pub struct BloomUpsamplingPipeline { pub bind_group_layout: BindGroupLayout, + /// The shader asset handle. + pub shader: Handle, } #[derive(PartialEq, Eq, Hash, Clone)] @@ -54,7 +56,10 @@ impl FromWorld for BloomUpsamplingPipeline { ), ); - BloomUpsamplingPipeline { bind_group_layout } + BloomUpsamplingPipeline { + bind_group_layout, + shader: load_embedded_asset!(world, "bloom.wgsl"), + } } } @@ -105,7 +110,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline { layout: vec![self.bind_group_layout.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![], entry_point: "upsample".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 67e5e7f1d8..d46174192b 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -18,9 +18,9 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; #[require( Camera, DebandDither, - CameraRenderGraph(|| CameraRenderGraph::new(Core2d)), - Projection(|| Projection::Orthographic(OrthographicProjection::default_2d())), - Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))), - Tonemapping(|| Tonemapping::None), + CameraRenderGraph::new(Core2d), + Projection::Orthographic(OrthographicProjection::default_2d()), + Frustum = OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default())), + Tonemapping::None, )] pub struct Camera2d; diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 3261ef5a61..725ac38ed9 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -19,6 +19,7 @@ pub mod graph { MainOpaquePass, MainTransparentPass, EndMainPass, + Wireframe, Bloom, PostProcessing, Tonemapping, @@ -33,7 +34,7 @@ pub mod graph { use core::ops::Range; use bevy_asset::UntypedAssetId; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, render_phase::PhaseItemBatchSetKey, @@ -64,7 +65,7 @@ use bevy_render::{ sync_world::MainEntity, texture::TextureCache, view::{Msaa, ViewDepthTexture}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use self::graph::{Core2d, Node2d}; @@ -92,8 +93,8 @@ impl Plugin for Core2dPlugin { .add_systems( Render, ( - sort_phase_system::.in_set(RenderSet::PhaseSort), - prepare_core_2d_depth_textures.in_set(RenderSet::PrepareResources), + sort_phase_system::.in_set(RenderSystems::PhaseSort), + prepare_core_2d_depth_textures.in_set(RenderSystems::PrepareResources), ), ); diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 337b157a0d..9bcb2b4f80 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -21,8 +21,8 @@ use serde::{Deserialize, Serialize}; #[reflect(Component, Default, Clone)] #[require( Camera, - DebandDither(|| DebandDither::Enabled), - CameraRenderGraph(|| CameraRenderGraph::new(Core3d)), + DebandDither::Enabled, + CameraRenderGraph::new(Core3d), Projection, Tonemapping, ColorGrading, @@ -56,7 +56,7 @@ pub struct Camera3d { /// /// Higher qualities are more GPU-intensive. /// - /// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin). + /// **Note:** You can get better-looking results at any quality level by enabling TAA. See: `TemporalAntiAliasPlugin` pub screen_space_specular_transmission_quality: ScreenSpaceTransmissionQuality, } @@ -117,7 +117,7 @@ impl From for LoadOp { /// /// Higher qualities are more GPU-intensive. /// -/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin). +/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: `TemporalAntiAliasPlugin` #[derive(Resource, Default, Clone, Copy, Reflect, PartialEq, PartialOrd, Debug)] #[reflect(Resource, Default, Clone, Debug, PartialEq)] pub enum ScreenSpaceTransmissionQuality { diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index fa092b7fd4..0ff61db842 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -28,6 +28,7 @@ pub mod graph { MainTransmissivePass, MainTransparentPass, EndMainPass, + Wireframe, LateDownsampleDepth, Taa, MotionBlur, @@ -86,7 +87,7 @@ use bevy_color::LinearRgba; use bevy_ecs::prelude::*; use bevy_image::BevyDefault; use bevy_math::FloatOrd; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::ExtractComponentPlugin, @@ -105,7 +106,7 @@ use bevy_render::{ sync_world::{MainEntity, RenderEntity}, texture::{ColorAttachment, TextureCache}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use nonmax::NonMaxU32; use tracing::warn; @@ -166,14 +167,14 @@ impl Plugin for Core3dPlugin { .add_systems( Render, ( - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSystems::PhaseSort), + sort_phase_system::.in_set(RenderSystems::PhaseSort), configure_occlusion_culling_view_targets .after(prepare_view_targets) - .in_set(RenderSet::ManageViews), - prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources), - prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources), - prepare_prepass_textures.in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::ManageViews), + prepare_core_3d_depth_textures.in_set(RenderSystems::PrepareResources), + prepare_core_3d_transmission_textures.in_set(RenderSystems::PrepareResources), + prepare_prepass_textures.in_set(RenderSystems::PrepareResources), ), ); diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index 966be880c2..0e9465aafa 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -3,7 +3,7 @@ use crate::{ prepass::{DeferredPrepass, ViewPrepassTextures}, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset}; use bevy_ecs::prelude::*; use bevy_math::UVec2; use bevy_render::{ @@ -12,7 +12,7 @@ use bevy_render::{ renderer::RenderDevice, texture::{CachedTexture, TextureCache}, view::ViewTarget, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_ecs::query::QueryItem; @@ -23,24 +23,17 @@ use bevy_render::{ use super::DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT; -pub const COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE: Handle = - weak_handle!("70d91342-1c43-4b20-973f-aa6ce93aa617"); pub struct CopyDeferredLightingIdPlugin; impl Plugin for CopyDeferredLightingIdPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE, - "copy_deferred_lighting_id.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "copy_deferred_lighting_id.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app.add_systems( Render, - (prepare_deferred_lighting_id_textures.in_set(RenderSet::PrepareResources),), + (prepare_deferred_lighting_id_textures.in_set(RenderSystems::PrepareResources),), ); } @@ -137,6 +130,8 @@ impl FromWorld for CopyDeferredLightingIdPipeline { ), ); + let shader = load_embedded_asset!(world, "copy_deferred_lighting_id.wgsl"); + let pipeline_id = world .resource_mut::() @@ -145,7 +140,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline { layout: vec![layout.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE, + shader, shader_defs: vec![], entry_point: "fragment".into(), targets: vec![], diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index 87a10313f1..38f5e1e796 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -15,7 +15,7 @@ //! [Depth of field]: https://en.wikipedia.org/wiki/Depth_of_field use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -55,7 +55,7 @@ use bevy_render::{ prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_utils::{default, once}; use smallvec::SmallVec; @@ -69,8 +69,6 @@ use crate::{ fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; -const DOF_SHADER_HANDLE: Handle = weak_handle!("c3580ddc-2cbc-4535-a02b-9a2959066b52"); - /// A plugin that adds support for the depth of field effect to Bevy. pub struct DepthOfFieldPlugin; @@ -206,7 +204,7 @@ enum DofPass { impl Plugin for DepthOfFieldPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, DOF_SHADER_HANDLE, "dof.wgsl", Shader::from_wgsl); + embedded_asset!(app, "dof.wgsl"); app.register_type::(); app.register_type::(); @@ -229,7 +227,7 @@ impl Plugin for DepthOfFieldPlugin { prepare_auxiliary_depth_of_field_textures, ) .after(prepare_view_targets) - .in_set(RenderSet::ManageViews), + .in_set(RenderSystems::ManageViews), ) .add_systems( Render, @@ -238,11 +236,11 @@ impl Plugin for DepthOfFieldPlugin { prepare_depth_of_field_pipelines, ) .chain() - .in_set(RenderSet::Prepare), + .in_set(RenderSystems::Prepare), ) .add_systems( Render, - prepare_depth_of_field_global_bind_group.in_set(RenderSet::PrepareBindGroups), + prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups), ) .add_render_graph_node::>(Core3d, Node3d::DepthOfField) .add_render_graph_edges( @@ -327,6 +325,8 @@ pub struct DepthOfFieldPipeline { /// The bind group layout shared among all invocations of the depth of field /// shader. global_bind_group_layout: BindGroupLayout, + /// The shader asset handle. + shader: Handle, } impl ViewNode for DepthOfFieldNode { @@ -678,11 +678,13 @@ pub fn prepare_depth_of_field_pipelines( &ViewDepthOfFieldBindGroupLayouts, &Msaa, )>, + asset_server: Res, ) { for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() { let dof_pipeline = DepthOfFieldPipeline { view_bind_group_layouts: view_bind_group_layouts.clone(), global_bind_group_layout: global_bind_group_layout.layout.clone(), + shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"), }; // We'll need these two flags to create the `DepthOfFieldPipelineKey`s. @@ -800,7 +802,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline { depth_stencil: None, multisample: default(), fragment: Some(FragmentState { - shader: DOF_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: match key.pass { DofPass::GaussianHorizontal => "gaussian_horizontal".into(), diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index 487049f015..1223ed35ec 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -7,6 +7,10 @@ use core::array; +use crate::core_3d::{ + graph::{Core3d, Node3d}, + prepare_core_3d_depth_textures, +}; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::{Deref, DerefMut}; @@ -21,6 +25,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::{uvec2, UVec2, Vec4Swizzles as _}; +use bevy_render::batching::gpu_preprocessing::GpuPreprocessingSupport; use bevy_render::{ experimental::occlusion_culling::{ OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, @@ -30,23 +35,19 @@ use bevy_render::{ binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d}, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, ComputePipeline, ComputePipelineDescriptor, - DownlevelFlags, Extent3d, IntoBinding, PipelineCache, PushConstantRange, Sampler, - SamplerBindingType, SamplerDescriptor, Shader, ShaderStages, SpecializedComputePipeline, + Extent3d, IntoBinding, PipelineCache, PushConstantRange, Sampler, SamplerBindingType, + SamplerDescriptor, Shader, ShaderStages, SpecializedComputePipeline, SpecializedComputePipelines, StorageTextureAccess, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, }, - renderer::{RenderAdapter, RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice}, texture::TextureCache, view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bitflags::bitflags; - -use crate::core_3d::{ - graph::{Core3d, Node3d}, - prepare_core_3d_depth_textures, -}; +use tracing::debug; /// Identifies the `downsample_depth.wgsl` shader. pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle = @@ -102,7 +103,7 @@ impl Plugin for MipGenerationPlugin { ) .add_systems( Render, - create_downsample_depth_pipelines.in_set(RenderSet::Prepare), + create_downsample_depth_pipelines.in_set(RenderSystems::Prepare), ) .add_systems( Render, @@ -111,7 +112,7 @@ impl Plugin for MipGenerationPlugin { prepare_downsample_depth_view_bind_groups, ) .chain() - .in_set(RenderSet::PrepareResources) + .in_set(RenderSystems::PrepareResources) .run_if(resource_exists::) .after(prepare_core_3d_depth_textures), ); @@ -325,26 +326,14 @@ pub struct DownsampleDepthPipelines { sampler: Sampler, } -fn supports_compute_shaders(device: &RenderDevice, adapter: &RenderAdapter) -> bool { - adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::COMPUTE_SHADERS) - // Even if the adapter supports compute, we might be simulating a lack of - // compute via device limits (see `WgpuSettingsPriority::WebGL2` and - // `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the - // `max_compute_*` limits to zero, so we arbitrarily pick one as a canary. - && (device.limits().max_compute_workgroup_storage_size != 0) -} - /// Creates the [`DownsampleDepthPipelines`] if downsampling is supported on the /// current platform. fn create_downsample_depth_pipelines( mut commands: Commands, render_device: Res, - render_adapter: Res, pipeline_cache: Res, mut specialized_compute_pipelines: ResMut>, + gpu_preprocessing_support: Res, mut has_run: Local, ) { // Only run once. @@ -356,9 +345,8 @@ fn create_downsample_depth_pipelines( } *has_run = true; - // If we don't have compute shaders, we can't invoke the downsample depth - // compute shader. - if !supports_compute_shaders(&render_device, &render_adapter) { + if !gpu_preprocessing_support.is_culling_supported() { + debug!("Downsample depth is not supported on this platform."); return; } diff --git a/crates/bevy_core_pipeline/src/experimental/mod.rs b/crates/bevy_core_pipeline/src/experimental/mod.rs index 4f957477ea..071eb97d86 100644 --- a/crates/bevy_core_pipeline/src/experimental/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mod.rs @@ -5,7 +5,3 @@ //! are included nonetheless for testing purposes. pub mod mip_generation; - -pub mod taa { - pub use crate::taa::{TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing}; -} diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 49b9b7a20b..9e04614276 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -9,22 +9,18 @@ pub mod auto_exposure; pub mod blit; pub mod bloom; -pub mod contrast_adaptive_sharpening; pub mod core_2d; pub mod core_3d; pub mod deferred; pub mod dof; pub mod experimental; pub mod fullscreen_vertex_shader; -pub mod fxaa; pub mod motion_blur; pub mod msaa_writeback; pub mod oit; pub mod post_process; pub mod prepass; mod skybox; -pub mod smaa; -mod taa; pub mod tonemapping; pub mod upscaling; @@ -41,19 +37,16 @@ pub mod prelude { use crate::{ blit::BlitPlugin, bloom::BloomPlugin, - contrast_adaptive_sharpening::CasPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, dof::DepthOfFieldPlugin, experimental::mip_generation::MipGenerationPlugin, fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, - fxaa::FxaaPlugin, motion_blur::MotionBlurPlugin, msaa_writeback::MsaaWritebackPlugin, post_process::PostProcessingPlugin, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, - smaa::SmaaPlugin, tonemapping::TonemappingPlugin, upscaling::UpscalingPlugin, }; @@ -85,11 +78,8 @@ impl Plugin for CorePipelinePlugin { TonemappingPlugin, UpscalingPlugin, BloomPlugin, - FxaaPlugin, - CasPlugin, MotionBlurPlugin, DepthOfFieldPlugin, - SmaaPlugin, PostProcessingPlugin, OrderIndependentTransparencyPlugin, MipGenerationPlugin, diff --git a/crates/bevy_core_pipeline/src/motion_blur/mod.rs b/crates/bevy_core_pipeline/src/motion_blur/mod.rs index 313e001bc3..ecf6432c9f 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/mod.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/mod.rs @@ -7,17 +7,20 @@ use crate::{ prepass::{DepthPrepass, MotionVectorPrepass}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::embedded_asset; use bevy_ecs::{ - component::Component, query::With, reflect::ReflectComponent, schedule::IntoScheduleConfigs, + component::Component, + query::{QueryItem, With}, + reflect::ReflectComponent, + schedule::IntoScheduleConfigs, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::Camera, extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, render_graph::{RenderGraphApp, ViewNodeRunner}, - render_resource::{Shader, ShaderType, SpecializedRenderPipelines}, - Render, RenderApp, RenderSet, + render_resource::{ShaderType, SpecializedRenderPipelines}, + Render, RenderApp, RenderSystems, }; pub mod node; @@ -53,9 +56,8 @@ pub mod pipeline; /// )); /// # } /// ```` -#[derive(Reflect, Component, Clone, ExtractComponent, ShaderType)] +#[derive(Reflect, Component, Clone)] #[reflect(Component, Default, Clone)] -#[extract_component_filter(With)] #[require(DepthPrepass, MotionVectorPrepass)] pub struct MotionBlur { /// The strength of motion blur from `0.0` to `1.0`. @@ -88,9 +90,6 @@ pub struct MotionBlur { /// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is /// equivalent to disabling motion blur. pub samples: u32, - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - // WebGL2 structs must be 16 byte aligned. - pub _webgl2_padding: bevy_math::Vec2, } impl Default for MotionBlur { @@ -98,28 +97,44 @@ impl Default for MotionBlur { Self { shutter_angle: 0.5, samples: 1, - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - _webgl2_padding: Default::default(), } } } -pub const MOTION_BLUR_SHADER_HANDLE: Handle = - weak_handle!("d9ca74af-fa0a-4f11-b0f2-19613b618b93"); +impl ExtractComponent for MotionBlur { + type QueryData = &'static Self; + type QueryFilter = With; + type Out = MotionBlurUniform; + + fn extract_component(item: QueryItem) -> Option { + Some(MotionBlurUniform { + shutter_angle: item.shutter_angle, + samples: item.samples, + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + _webgl2_padding: Default::default(), + }) + } +} + +#[doc(hidden)] +#[derive(Component, ShaderType, Clone)] +pub struct MotionBlurUniform { + shutter_angle: f32, + samples: u32, + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + // WebGL2 structs must be 16 byte aligned. + _webgl2_padding: bevy_math::Vec2, +} /// Adds support for per-object motion blur to the app. See [`MotionBlur`] for details. pub struct MotionBlurPlugin; impl Plugin for MotionBlurPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - MOTION_BLUR_SHADER_HANDLE, - "motion_blur.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "motion_blur.wgsl"); + app.add_plugins(( ExtractComponentPlugin::::default(), - UniformComponentPlugin::::default(), + UniformComponentPlugin::::default(), )); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -130,7 +145,7 @@ impl Plugin for MotionBlurPlugin { .init_resource::>() .add_systems( Render, - pipeline::prepare_motion_blur_pipelines.in_set(RenderSet::Prepare), + pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare), ); render_app diff --git a/crates/bevy_core_pipeline/src/motion_blur/node.rs b/crates/bevy_core_pipeline/src/motion_blur/node.rs index 2497bd633d..ade5f50d77 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/node.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/node.rs @@ -15,7 +15,7 @@ use crate::prepass::ViewPrepassTextures; use super::{ pipeline::{MotionBlurPipeline, MotionBlurPipelineId}, - MotionBlur, + MotionBlurUniform, }; #[derive(Default)] @@ -26,7 +26,7 @@ impl ViewNode for MotionBlurNode { &'static ViewTarget, &'static MotionBlurPipelineId, &'static ViewPrepassTextures, - &'static MotionBlur, + &'static MotionBlurUniform, &'static Msaa, ); fn run( @@ -42,7 +42,7 @@ impl ViewNode for MotionBlurNode { let motion_blur_pipeline = world.resource::(); let pipeline_cache = world.resource::(); - let settings_uniforms = world.resource::>(); + let settings_uniforms = world.resource::>(); let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else { return Ok(()); }; diff --git a/crates/bevy_core_pipeline/src/motion_blur/pipeline.rs b/crates/bevy_core_pipeline/src/motion_blur/pipeline.rs index 61bb7b60ce..dfd4bca103 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/pipeline.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/pipeline.rs @@ -1,3 +1,4 @@ +use bevy_asset::{load_embedded_asset, Handle}; use bevy_ecs::{ component::Component, entity::Entity, @@ -16,9 +17,9 @@ use bevy_render::{ }, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState, MultisampleState, PipelineCache, PrimitiveState, - RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderDefVal, - ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, - TextureFormat, TextureSampleType, + RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader, + ShaderDefVal, ShaderStages, ShaderType, SpecializedRenderPipeline, + SpecializedRenderPipelines, TextureFormat, TextureSampleType, }, renderer::RenderDevice, view::{ExtractedView, Msaa, ViewTarget}, @@ -26,17 +27,18 @@ use bevy_render::{ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; -use super::{MotionBlur, MOTION_BLUR_SHADER_HANDLE}; +use super::MotionBlurUniform; #[derive(Resource)] pub struct MotionBlurPipeline { pub(crate) sampler: Sampler, pub(crate) layout: BindGroupLayout, pub(crate) layout_msaa: BindGroupLayout, + pub(crate) shader: Handle, } impl MotionBlurPipeline { - pub(crate) fn new(render_device: &RenderDevice) -> Self { + pub(crate) fn new(render_device: &RenderDevice, shader: Handle) -> Self { let mb_layout = &BindGroupLayoutEntries::sequential( ShaderStages::FRAGMENT, ( @@ -49,7 +51,7 @@ impl MotionBlurPipeline { // Linear Sampler sampler(SamplerBindingType::Filtering), // Motion blur settings uniform input - uniform_buffer_sized(false, Some(MotionBlur::min_size())), + uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())), // Globals uniform input uniform_buffer_sized(false, Some(GlobalsUniform::min_size())), ), @@ -67,7 +69,7 @@ impl MotionBlurPipeline { // Linear Sampler sampler(SamplerBindingType::Filtering), // Motion blur settings uniform input - uniform_buffer_sized(false, Some(MotionBlur::min_size())), + uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())), // Globals uniform input uniform_buffer_sized(false, Some(GlobalsUniform::min_size())), ), @@ -82,6 +84,7 @@ impl MotionBlurPipeline { sampler, layout, layout_msaa, + shader, } } } @@ -89,7 +92,9 @@ impl MotionBlurPipeline { impl FromWorld for MotionBlurPipeline { fn from_world(render_world: &mut bevy_ecs::world::World) -> Self { let render_device = render_world.resource::().clone(); - MotionBlurPipeline::new(&render_device) + + let shader = load_embedded_asset!(render_world, "motion_blur.wgsl"); + MotionBlurPipeline::new(&render_device, shader) } } @@ -125,7 +130,7 @@ impl SpecializedRenderPipeline for MotionBlurPipeline { layout, vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: MOTION_BLUR_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -155,7 +160,7 @@ pub(crate) fn prepare_motion_blur_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &ExtractedView, &Msaa), With>, + views: Query<(Entity, &ExtractedView, &Msaa), With>, ) { for (entity, view, msaa) in &views { let pipeline_id = pipelines.specialize( diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index f9c543aeff..8dc51e4ed5 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -12,7 +12,7 @@ use bevy_render::{ render_resource::*, renderer::RenderContext, view::{Msaa, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; /// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras @@ -26,7 +26,7 @@ impl Plugin for MsaaWritebackPlugin { }; render_app.add_systems( Render, - prepare_msaa_writeback_pipelines.in_set(RenderSet::Prepare), + prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), ); { render_app diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index f4337065cf..3ae95d71cc 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -1,22 +1,20 @@ //! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details. use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{component::*, prelude::*}; use bevy_math::UVec2; -use bevy_platform_support::collections::HashSet; -use bevy_platform_support::time::Instant; +use bevy_platform::collections::HashSet; +use bevy_platform::time::Instant; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::{ExtractComponent, ExtractComponentPlugin}, + load_shader_library, render_graph::{RenderGraphApp, ViewNodeRunner}, - render_resource::{ - BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages, - }, + render_resource::{BufferUsages, BufferVec, DynamicUniformBuffer, ShaderType, TextureUsages}, renderer::{RenderDevice, RenderQueue}, view::Msaa, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_window::PrimaryWindow; use resolve::{ @@ -33,10 +31,6 @@ use crate::core_3d::{ /// Module that defines the necessary systems to resolve the OIT buffer and render it to the screen. pub mod resolve; -/// Shader handle for the shader that draws the transparent meshes to the OIT layers buffer. -pub const OIT_DRAW_SHADER_HANDLE: Handle = - weak_handle!("0cd3c764-39b8-437b-86b4-4e45635fc03d"); - /// Used to identify which camera will use OIT to render transparent meshes /// and to configure OIT. // TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT, @@ -105,12 +99,7 @@ impl Component for OrderIndependentTransparencySettings { pub struct OrderIndependentTransparencyPlugin; impl Plugin for OrderIndependentTransparencyPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - OIT_DRAW_SHADER_HANDLE, - "oit_draw.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "oit_draw.wgsl"); app.add_plugins(( ExtractComponentPlugin::::default(), @@ -126,7 +115,7 @@ impl Plugin for OrderIndependentTransparencyPlugin { render_app.add_systems( Render, - prepare_oit_buffers.in_set(RenderSet::PrepareResources), + prepare_oit_buffers.in_set(RenderSystems::PrepareResources), ); render_app diff --git a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs index 95ded7a5c0..fe62d0c9b1 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs @@ -3,10 +3,10 @@ use crate::{ oit::OrderIndependentTransparencySettings, }; use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer}; use bevy_derive::Deref; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, + entity::{EntityHashMap, EntityHashSet}, prelude::*, }; use bevy_image::BevyDefault as _; @@ -16,20 +16,16 @@ use bevy_render::{ BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, BlendComponent, BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, DownlevelFlags, FragmentState, MultisampleState, PipelineCache, PrimitiveState, RenderPipelineDescriptor, - Shader, ShaderDefVal, ShaderStages, TextureFormat, + ShaderDefVal, ShaderStages, TextureFormat, }, renderer::{RenderAdapter, RenderDevice}, view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use tracing::warn; use super::OitBuffers; -/// Shader handle for the shader that sorts the OIT layers, blends the colors based on depth and renders them to the screen. -pub const OIT_RESOLVE_SHADER_HANDLE: Handle = - weak_handle!("562d2917-eb06-444d-9ade-41de76b0f5ae"); - /// Contains the render node used to run the resolve pass. pub mod node; @@ -40,12 +36,7 @@ pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2; pub struct OitResolvePlugin; impl Plugin for OitResolvePlugin { fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( - app, - OIT_RESOLVE_SHADER_HANDLE, - "oit_resolve.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "oit_resolve.wgsl"); } fn finish(&self, app: &mut bevy_app::App) { @@ -65,8 +56,8 @@ impl Plugin for OitResolvePlugin { .add_systems( Render, ( - queue_oit_resolve_pipeline.in_set(RenderSet::Queue), - prepare_oit_resolve_bind_group.in_set(RenderSet::PrepareBindGroups), + queue_oit_resolve_pipeline.in_set(RenderSystems::Queue), + prepare_oit_resolve_bind_group.in_set(RenderSystems::PrepareBindGroups), ), ) .init_resource::(); @@ -165,6 +156,7 @@ pub fn queue_oit_resolve_pipeline( ), With, >, + asset_server: Res, // Store the key with the id to make the clean up logic easier. // This also means it will always replace the entry if the key changes so nothing to clean up. mut cached_pipeline_id: Local>, @@ -184,7 +176,7 @@ pub fn queue_oit_resolve_pipeline( } } - let desc = specialize_oit_resolve_pipeline(key, &resolve_pipeline); + let desc = specialize_oit_resolve_pipeline(key, &resolve_pipeline, &asset_server); let pipeline_id = pipeline_cache.queue_render_pipeline(desc); commands.entity(e).insert(OitResolvePipelineId(pipeline_id)); @@ -202,6 +194,7 @@ pub fn queue_oit_resolve_pipeline( fn specialize_oit_resolve_pipeline( key: OitResolvePipelineKey, resolve_pipeline: &OitResolvePipeline, + asset_server: &AssetServer, ) -> RenderPipelineDescriptor { let format = if key.hdr { ViewTarget::TEXTURE_FORMAT_HDR @@ -217,7 +210,7 @@ fn specialize_oit_resolve_pipeline( ], fragment: Some(FragmentState { entry_point: "fragment".into(), - shader: OIT_RESOLVE_SHADER_HANDLE, + shader: load_embedded_asset!(asset_server, "oit_resolve.wgsl"), shader_defs: vec![ShaderDefVal::UInt( "LAYER_COUNT".into(), key.layer_count as u32, diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 2ac03c08c8..1ab03c5dfa 100644 --- a/crates/bevy_core_pipeline/src/post_process/mod.rs +++ b/crates/bevy_core_pipeline/src/post_process/mod.rs @@ -3,7 +3,7 @@ //! Currently, this consists only of chromatic aberration. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Assets, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, weak_handle, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -20,6 +20,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::Camera, extract_component::{ExtractComponent, ExtractComponentPlugin}, + load_shader_library, render_asset::{RenderAssetUsages, RenderAssets}, render_graph::{ NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner, @@ -36,7 +37,7 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice, RenderQueue}, texture::GpuImage, view::{ExtractedView, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_utils::prelude::default; @@ -46,13 +47,6 @@ use crate::{ fullscreen_vertex_shader, }; -/// The handle to the built-in postprocessing shader `post_process.wgsl`. -const POST_PROCESSING_SHADER_HANDLE: Handle = - weak_handle!("5e8e627a-7531-484d-a988-9a38acb34e52"); -/// The handle to the chromatic aberration shader `chromatic_aberration.wgsl`. -const CHROMATIC_ABERRATION_SHADER_HANDLE: Handle = - weak_handle!("e598550e-71c3-4f5a-ba29-aebc3f88c7b5"); - /// The handle to the default chromatic aberration lookup texture. /// /// This is just a 3x1 image consisting of one red pixel, one green pixel, and @@ -136,6 +130,8 @@ pub struct PostProcessingPipeline { source_sampler: Sampler, /// Specifies how to sample the chromatic aberration gradient. chromatic_aberration_lut_sampler: Sampler, + /// The shader asset handle. + shader: Handle, } /// A key that uniquely identifies a built-in postprocessing pipeline. @@ -188,18 +184,9 @@ pub struct PostProcessingNode; impl Plugin for PostProcessingPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - POST_PROCESSING_SHADER_HANDLE, - "post_process.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - CHROMATIC_ABERRATION_SHADER_HANDLE, - "chromatic_aberration.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "chromatic_aberration.wgsl"); + + embedded_asset!(app, "post_process.wgsl"); // Load the default chromatic aberration LUT. let mut assets = app.world_mut().resource_mut::>(); @@ -234,7 +221,7 @@ impl Plugin for PostProcessingPlugin { prepare_post_processing_pipelines, prepare_post_processing_uniforms, ) - .in_set(RenderSet::Prepare), + .in_set(RenderSystems::Prepare), ) .add_render_graph_node::>( Core3d, @@ -321,6 +308,7 @@ impl FromWorld for PostProcessingPipeline { bind_group_layout, source_sampler, chromatic_aberration_lut_sampler, + shader: load_embedded_asset!(world, "post_process.wgsl"), } } } @@ -334,7 +322,7 @@ impl SpecializedRenderPipeline for PostProcessingPipeline { layout: vec![self.bind_group_layout.clone()], vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: POST_PROCESSING_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![], entry_point: "fragment_main".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index 7e2dba466c..cb75df2053 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -1,5 +1,5 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_ecs::{ prelude::{Component, Entity}, query::{QueryItem, With}, @@ -25,28 +25,21 @@ use bevy_render::{ renderer::RenderDevice, texture::GpuImage, view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_transform::components::Transform; -use prepass::{SkyboxPrepassPipeline, SKYBOX_PREPASS_SHADER_HANDLE}; +use prepass::SkyboxPrepassPipeline; use crate::{core_3d::CORE_3D_DEPTH_FORMAT, prepass::PreviousViewUniforms}; -const SKYBOX_SHADER_HANDLE: Handle = weak_handle!("a66cf9cc-cab8-47f8-ac32-db82fdc4f29b"); - pub mod prepass; pub struct SkyboxPlugin; impl Plugin for SkyboxPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, SKYBOX_SHADER_HANDLE, "skybox.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - SKYBOX_PREPASS_SHADER_HANDLE, - "skybox_prepass.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "skybox.wgsl"); + embedded_asset!(app, "skybox_prepass.wgsl"); app.register_type::().add_plugins(( ExtractComponentPlugin::::default(), @@ -63,11 +56,11 @@ impl Plugin for SkyboxPlugin { .add_systems( Render, ( - prepare_skybox_pipelines.in_set(RenderSet::Prepare), - prepass::prepare_skybox_prepass_pipelines.in_set(RenderSet::Prepare), - prepare_skybox_bind_groups.in_set(RenderSet::PrepareBindGroups), + prepare_skybox_pipelines.in_set(RenderSystems::Prepare), + prepass::prepare_skybox_prepass_pipelines.in_set(RenderSystems::Prepare), + prepare_skybox_bind_groups.in_set(RenderSystems::PrepareBindGroups), prepass::prepare_skybox_prepass_bind_groups - .in_set(RenderSet::PrepareBindGroups), + .in_set(RenderSystems::PrepareBindGroups), ), ); } @@ -76,9 +69,10 @@ impl Plugin for SkyboxPlugin { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; + let shader = load_embedded_asset!(render_app.world(), "skybox.wgsl"); let render_device = render_app.world().resource::().clone(); render_app - .insert_resource(SkyboxPipeline::new(&render_device)) + .insert_resource(SkyboxPipeline::new(&render_device, shader)) .init_resource::(); } } @@ -158,10 +152,11 @@ pub struct SkyboxUniforms { #[derive(Resource)] struct SkyboxPipeline { bind_group_layout: BindGroupLayout, + shader: Handle, } impl SkyboxPipeline { - fn new(render_device: &RenderDevice) -> Self { + fn new(render_device: &RenderDevice, shader: Handle) -> Self { Self { bind_group_layout: render_device.create_bind_group_layout( "skybox_bind_group_layout", @@ -176,6 +171,7 @@ impl SkyboxPipeline { ), ), ), + shader, } } } @@ -196,7 +192,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline { layout: vec![self.bind_group_layout.clone()], push_constant_ranges: Vec::new(), vertex: VertexState { - shader: SKYBOX_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: Vec::new(), entry_point: "skybox_vertex".into(), buffers: Vec::new(), @@ -224,7 +220,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline { alpha_to_coverage_enabled: false, }, fragment: Some(FragmentState { - shader: SKYBOX_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: Vec::new(), entry_point: "skybox_fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/skybox/prepass.rs b/crates/bevy_core_pipeline/src/skybox/prepass.rs index 658660bbc6..a027f69f93 100644 --- a/crates/bevy_core_pipeline/src/skybox/prepass.rs +++ b/crates/bevy_core_pipeline/src/skybox/prepass.rs @@ -1,6 +1,6 @@ //! Adds motion vector support to skyboxes. See [`SkyboxPrepassPipeline`] for details. -use bevy_asset::{weak_handle, Handle}; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_ecs::{ component::Component, entity::Entity, @@ -30,9 +30,6 @@ use crate::{ Skybox, }; -pub const SKYBOX_PREPASS_SHADER_HANDLE: Handle = - weak_handle!("7a292435-bfe6-4ed9-8d30-73bf7aa673b0"); - /// This pipeline writes motion vectors to the prepass for all [`Skybox`]es. /// /// This allows features like motion blur and TAA to work correctly on the skybox. Without this, for @@ -41,6 +38,7 @@ pub const SKYBOX_PREPASS_SHADER_HANDLE: Handle = #[derive(Resource)] pub struct SkyboxPrepassPipeline { bind_group_layout: BindGroupLayout, + shader: Handle, } /// Used to specialize the [`SkyboxPrepassPipeline`]. @@ -75,6 +73,7 @@ impl FromWorld for SkyboxPrepassPipeline { ), ), ), + shader: load_embedded_asset!(world, "skybox_prepass.wgsl"), } } } @@ -102,7 +101,7 @@ impl SpecializedRenderPipeline for SkyboxPrepassPipeline { alpha_to_coverage_enabled: false, }, fragment: Some(FragmentState { - shader: SKYBOX_PREPASS_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![], entry_point: "fragment".into(), targets: prepass_target_descriptors(key.normal_prepass, true, false), diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 443b81327a..19ac3ef7e2 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -1,6 +1,6 @@ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Assets, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Assets, Handle}; use bevy_ecs::prelude::*; use bevy_image::{CompressedImageFormats, Image, ImageSampler, ImageType}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -8,6 +8,7 @@ use bevy_render::{ camera::Camera, extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_resource::{ExtractResource, ExtractResourcePlugin}, + load_shader_library, render_asset::{RenderAssetUsages, RenderAssets}, render_resource::{ binding_types::{sampler, texture_2d, texture_3d, uniform_buffer}, @@ -16,7 +17,7 @@ use bevy_render::{ renderer::RenderDevice, texture::{FallbackImage, GpuImage}, view::{ExtractedView, ViewTarget, ViewUniform}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bitflags::bitflags; #[cfg(not(feature = "tonemapping_luts"))] @@ -27,45 +28,22 @@ mod node; use bevy_utils::default; pub use node::TonemappingNode; -const TONEMAPPING_SHADER_HANDLE: Handle = - weak_handle!("e239c010-c25c-42a1-b4e8-08818764d667"); - -const TONEMAPPING_SHARED_SHADER_HANDLE: Handle = - weak_handle!("61dbc544-4b30-4ca9-83bd-4751b5cfb1b1"); - -const TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE: Handle = - weak_handle!("d50e3a70-c85e-4725-a81e-72fc83281145"); - /// 3D LUT (look up table) textures used for tonemapping #[derive(Resource, Clone, ExtractResource)] pub struct TonemappingLuts { - blender_filmic: Handle, - agx: Handle, - tony_mc_mapface: Handle, + pub blender_filmic: Handle, + pub agx: Handle, + pub tony_mc_mapface: Handle, } pub struct TonemappingPlugin; impl Plugin for TonemappingPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - TONEMAPPING_SHADER_HANDLE, - "tonemapping.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - TONEMAPPING_SHARED_SHADER_HANDLE, - "tonemapping_shared.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE, - "lut_bindings.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "tonemapping_shared.wgsl"); + load_shader_library!(app, "lut_bindings.wgsl"); + + embedded_asset!(app, "tonemapping.wgsl"); if !app.world().is_resource_added::() { let mut images = app.world_mut().resource_mut::>(); @@ -118,7 +96,7 @@ impl Plugin for TonemappingPlugin { .init_resource::>() .add_systems( Render, - prepare_view_tonemapping_pipelines.in_set(RenderSet::Prepare), + prepare_view_tonemapping_pipelines.in_set(RenderSystems::Prepare), ); } @@ -134,6 +112,7 @@ impl Plugin for TonemappingPlugin { pub struct TonemappingPipeline { texture_bind_group: BindGroupLayout, sampler: Sampler, + shader: Handle, } /// Optionally enables a tonemapping shader that attempts to map linear input stimulus into a perceptually uniform image for a given [`Camera`] entity. @@ -296,7 +275,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { layout: vec![self.texture_bind_group.clone()], vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: TONEMAPPING_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -340,6 +319,7 @@ impl FromWorld for TonemappingPipeline { TonemappingPipeline { texture_bind_group: tonemap_texture_bind_group, sampler, + shader: load_embedded_asset!(render_world, "tonemapping.wgsl"), } } } @@ -449,8 +429,6 @@ fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image { ..default() }); Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - "Tonemapping LUT sampler".to_string(), bytes, image_type, CompressedImageFormats::NONE, diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 89f3f8d09e..4ce91de393 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -1,12 +1,12 @@ use crate::blit::{BlitPipeline, BlitPipelineKey}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_render::{ camera::{CameraOutputMode, ExtractedCamera}, render_resource::*, view::ViewTarget, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; mod node; @@ -26,7 +26,7 @@ impl Plugin for UpscalingPlugin { // and aversion to extensive and intrusive system ordering. // See https://github.com/bevyengine/bevy/issues/14770 for more context. prepare_view_upscaling_pipelines - .in_set(RenderSet::Prepare) + .in_set(RenderSystems::Prepare) .ambiguous_with_all(), ); } diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index 2636ffc57d..e446d0f50d 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -205,8 +205,6 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { pub fn derive_app_label(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); let mut trait_path = BevyManifest::shared().get_path("bevy_app"); - let mut dyn_eq_path = trait_path.clone(); trait_path.segments.push(format_ident!("AppLabel").into()); - dyn_eq_path.segments.push(format_ident!("DynEq").into()); - derive_label(input, "AppLabel", &trait_path, &dyn_eq_path) + derive_label(input, "AppLabel", &trait_path) } diff --git a/crates/bevy_dev_tools/src/ci_testing/mod.rs b/crates/bevy_dev_tools/src/ci_testing/mod.rs index 09f16d71a7..faf99b1a71 100644 --- a/crates/bevy_dev_tools/src/ci_testing/mod.rs +++ b/crates/bevy_dev_tools/src/ci_testing/mod.rs @@ -57,7 +57,7 @@ impl Plugin for CiTestingPlugin { systems::send_events .before(trigger_screenshots) .before(bevy_window::close_when_requested) - .in_set(SendEvents) + .in_set(EventSenderSystems) .ambiguous_with_all(), ); @@ -66,10 +66,10 @@ impl Plugin for CiTestingPlugin { #[cfg(any(unix, windows))] app.configure_sets( Update, - SendEvents.before(bevy_app::TerminalCtrlCHandlerPlugin::exit_on_flag), + EventSenderSystems.before(bevy_app::TerminalCtrlCHandlerPlugin::exit_on_flag), ); } } #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] -struct SendEvents; +struct EventSenderSystems; diff --git a/crates/bevy_dev_tools/src/picking_debug.rs b/crates/bevy_dev_tools/src/picking_debug.rs index f72b70fc88..79c1c8fff4 100644 --- a/crates/bevy_dev_tools/src/picking_debug.rs +++ b/crates/bevy_dev_tools/src/picking_debug.rs @@ -1,14 +1,13 @@ //! Text and on-screen debugging tools use bevy_app::prelude::*; -use bevy_asset::prelude::*; use bevy_color::prelude::*; use bevy_ecs::prelude::*; use bevy_picking::backend::HitData; use bevy_picking::hover::HoverMap; use bevy_picking::pointer::{Location, PointerId, PointerPress}; use bevy_picking::prelude::*; -use bevy_picking::{pointer, PickSet}; +use bevy_picking::{pointer, PickingSystems}; use bevy_reflect::prelude::*; use bevy_render::prelude::*; use bevy_text::prelude::*; @@ -85,7 +84,7 @@ impl Plugin for DebugPickingPlugin { app.init_resource::() .add_systems( PreUpdate, - pointer_debug_visibility.in_set(PickSet::PostHover), + pointer_debug_visibility.in_set(PickingSystems::PostHover), ) .add_systems( PreUpdate, @@ -108,7 +107,7 @@ impl Plugin for DebugPickingPlugin { log_pointer_event_debug::, ) .distributive_run_if(DebugPickingMode::is_enabled) - .in_set(PickSet::Last), + .in_set(PickingSystems::Last), ); app.add_systems( @@ -116,7 +115,7 @@ impl Plugin for DebugPickingPlugin { (add_pointer_debug, update_debug_data, debug_draw) .chain() .distributive_run_if(DebugPickingMode::is_enabled) - .in_set(PickSet::Last), + .in_set(PickingSystems::Last), ); } } @@ -248,25 +247,18 @@ pub fn debug_draw( pointers: Query<(Entity, &PointerId, &PointerDebug)>, scale: Res, ) { - let font_handle: Handle = Default::default(); - for (entity, id, debug) in pointers.iter() { + for (entity, id, debug) in &pointers { let Some(pointer_location) = &debug.location else { continue; }; let text = format!("{id:?}\n{debug}"); - for camera in camera_query - .iter() - .map(|(entity, camera)| { - ( - entity, - camera.target.normalize(primary_window.single().ok()), - ) - }) - .filter_map(|(entity, target)| Some(entity).zip(target)) - .filter(|(_entity, target)| target == &pointer_location.target) - .map(|(cam_entity, _target)| cam_entity) - { + for (camera, _) in camera_query.iter().filter(|(_, camera)| { + camera + .target + .normalize(primary_window.single().ok()) + .is_some_and(|target| target == pointer_location.target) + }) { let mut pointer_pos = pointer_location.position; if let Some(viewport) = camera_query .get(camera) @@ -278,23 +270,21 @@ pub fn debug_draw( commands .entity(entity) + .despawn_related::() .insert(( - Text::new(text.clone()), - TextFont { - font: font_handle.clone(), - font_size: 12.0, - ..Default::default() - }, - TextColor(Color::WHITE), Node { position_type: PositionType::Absolute, left: Val::Px(pointer_pos.x + 5.0) / scale.0, top: Val::Px(pointer_pos.y + 5.0) / scale.0, + padding: UiRect::px(10.0, 10.0, 8.0, 6.0), ..Default::default() }, - )) - .insert(Pickable::IGNORE) - .insert(UiTargetCamera(camera)); + BackgroundColor(Color::BLACK.with_alpha(0.75)), + GlobalZIndex(i32::MAX), + Pickable::IGNORE, + UiTargetCamera(camera), + children![(Text::new(text.clone()), TextFont::from_font_size(12.0))], + )); } } } diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index 0fc784db75..2b89e5759e 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -18,8 +18,7 @@ serialize = [ "dep:serde", "bevy_ecs/serialize", "bevy_time/serialize", - "bevy_utils/serde", - "bevy_platform_support/serialize", + "bevy_platform/serialize", ] ## Disables diagnostics that are unsupported when Bevy is dynamically linked @@ -37,9 +36,8 @@ std = [ "serde?/std", "bevy_ecs/std", "bevy_app/std", - "bevy_platform_support/std", + "bevy_platform/std", "bevy_time/std", - "bevy_utils/std", "bevy_tasks/std", ] @@ -48,9 +46,8 @@ std = [ critical-section = [ "bevy_ecs/critical-section", "bevy_app/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", "bevy_time/critical-section", - "bevy_utils/critical-section", "bevy_tasks/critical-section", ] @@ -59,11 +56,9 @@ critical-section = [ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = false } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "alloc", ] } @@ -77,14 +72,14 @@ log = { version = "0.4", default-features = false } # macOS [target.'cfg(all(target_os="macos"))'.dependencies] # Some features of sysinfo are not supported by apple. This will disable those features on apple devices -sysinfo = { version = "0.33.0", optional = true, default-features = false, features = [ +sysinfo = { version = "0.35.0", optional = true, default-features = false, features = [ "apple-app-store", "system", ] } # Only include when on linux/windows/android/freebsd [target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "android", target_os = "freebsd"))'.dependencies] -sysinfo = { version = "0.33.0", optional = true, default-features = false, features = [ +sysinfo = { version = "0.35.0", optional = true, default-features = false, features = [ "system", ] } diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index af9a3e71e0..1f67d5220a 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -7,7 +7,7 @@ use core::{ use bevy_app::{App, SubApp}; use bevy_ecs::resource::Resource; use bevy_ecs::system::{Deferred, Res, SystemBuffer, SystemParam}; -use bevy_platform_support::{collections::HashMap, hash::PassHash, time::Instant}; +use bevy_platform::{collections::HashMap, hash::PassHash, time::Instant}; use const_fnv1a_hash::fnv1a_hash_str_64; use crate::DEFAULT_MAX_HISTORY_LENGTH; @@ -113,7 +113,9 @@ impl core::fmt::Display for DiagnosticPath { /// A single measurement of a [`Diagnostic`]. #[derive(Debug)] pub struct DiagnosticMeasurement { + /// When this measurement was taken. pub time: Instant, + /// Value of the measurement. pub value: f64, } @@ -122,12 +124,14 @@ pub struct DiagnosticMeasurement { #[derive(Debug)] pub struct Diagnostic { path: DiagnosticPath, + /// Suffix to use when logging measurements for this [`Diagnostic`], for example to show units. pub suffix: Cow<'static, str>, history: VecDeque, sum: f64, ema: f64, ema_smoothing_factor: f64, max_history_length: usize, + /// Disabled [`Diagnostic`]s are not measured or logged. pub is_enabled: bool, } @@ -219,6 +223,7 @@ impl Diagnostic { self } + /// Get the [`DiagnosticPath`] that identifies this [`Diagnostic`]. pub fn path(&self) -> &DiagnosticPath { &self.path } @@ -282,10 +287,12 @@ impl Diagnostic { self.max_history_length } + /// All measured values from this [`Diagnostic`], up to the configured maximum history length. pub fn values(&self) -> impl Iterator { self.history.iter().map(|x| &x.value) } + /// All measurements from this [`Diagnostic`], up to the configured maximum history length. pub fn measurements(&self) -> impl Iterator { self.history.iter() } @@ -293,6 +300,8 @@ impl Diagnostic { /// Clear the history of this diagnostic. pub fn clear_history(&mut self) { self.history.clear(); + self.sum = 0.0; + self.ema = 0.0; } } @@ -310,10 +319,12 @@ impl DiagnosticsStore { self.diagnostics.insert(diagnostic.path.clone(), diagnostic); } + /// Get the [`DiagnosticMeasurement`] with the given [`DiagnosticPath`], if it exists. pub fn get(&self, path: &DiagnosticPath) -> Option<&Diagnostic> { self.diagnostics.get(path) } + /// Mutably get the [`DiagnosticMeasurement`] with the given [`DiagnosticPath`], if it exists. pub fn get_mut(&mut self, path: &DiagnosticPath) -> Option<&mut Diagnostic> { self.diagnostics.get_mut(path) } @@ -420,3 +431,31 @@ impl RegisterDiagnostic for App { self } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clear_history() { + const MEASUREMENT: f64 = 20.0; + + let mut diagnostic = + Diagnostic::new(DiagnosticPath::new("test")).with_max_history_length(5); + let mut now = Instant::now(); + + for _ in 0..3 { + for _ in 0..5 { + diagnostic.add_measurement(DiagnosticMeasurement { + time: now, + value: MEASUREMENT, + }); + // Increase time to test smoothed average. + now += Duration::from_secs(1); + } + assert!((diagnostic.average().unwrap() - MEASUREMENT).abs() < 0.1); + assert!((diagnostic.smoothed().unwrap() - MEASUREMENT).abs() < 0.1); + diagnostic.clear_history(); + } + } +} diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index e5098d6c6f..588b3276f6 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -29,7 +29,7 @@ pub use diagnostic::*; pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use frame_count_diagnostics_plugin::{update_frame_count, FrameCount, FrameCountPlugin}; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; -pub use log_diagnostics_plugin::LogDiagnosticsPlugin; +pub use log_diagnostics_plugin::{LogDiagnosticsPlugin, LogDiagnosticsState}; #[cfg(feature = "sysinfo_plugin")] pub use system_information_diagnostics_plugin::{SystemInfo, SystemInformationDiagnosticsPlugin}; diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index 6a8c761c0b..4175eb395e 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -1,7 +1,8 @@ use super::{Diagnostic, DiagnosticPath, DiagnosticsStore}; -use alloc::vec::Vec; + use bevy_app::prelude::*; use bevy_ecs::prelude::*; +use bevy_platform::collections::HashSet; use bevy_time::{Real, Time, Timer, TimerMode}; use core::time::Duration; use log::{debug, info}; @@ -14,16 +15,76 @@ use log::{debug, info}; /// /// When no diagnostics are provided, this plugin does nothing. pub struct LogDiagnosticsPlugin { + /// If `true` then the `Debug` representation of each `Diagnostic` is logged. + /// If `false` then a (smoothed) current value and historical average are logged. + /// + /// Defaults to `false`. pub debug: bool, + /// Time to wait between logging diagnostics and logging them again. pub wait_duration: Duration, - pub filter: Option>, + /// If `Some` then only these diagnostics are logged. + pub filter: Option>, } /// State used by the [`LogDiagnosticsPlugin`] #[derive(Resource)] -struct LogDiagnosticsState { +pub struct LogDiagnosticsState { timer: Timer, - filter: Option>, + filter: Option>, +} + +impl LogDiagnosticsState { + /// Sets a new duration for the log timer + pub fn set_timer_duration(&mut self, duration: Duration) { + self.timer.set_duration(duration); + self.timer.set_elapsed(Duration::ZERO); + } + + /// Add a filter to the log state, returning `true` if the [`DiagnosticPath`] + /// was not present + pub fn add_filter(&mut self, diagnostic_path: DiagnosticPath) -> bool { + if let Some(filter) = &mut self.filter { + filter.insert(diagnostic_path) + } else { + self.filter = Some(HashSet::from_iter([diagnostic_path])); + true + } + } + + /// Extends the filter of the log state with multiple [`DiagnosticPaths`](DiagnosticPath) + pub fn extend_filter(&mut self, iter: impl IntoIterator) { + if let Some(filter) = &mut self.filter { + filter.extend(iter); + } else { + self.filter = Some(HashSet::from_iter(iter)); + } + } + + /// Removes a filter from the log state, returning `true` if it was present + pub fn remove_filter(&mut self, diagnostic_path: &DiagnosticPath) -> bool { + if let Some(filter) = &mut self.filter { + filter.remove(diagnostic_path) + } else { + false + } + } + + /// Clears the filters of the log state + pub fn clear_filter(&mut self) { + if let Some(filter) = &mut self.filter { + filter.clear(); + } + } + + /// Enables filtering with empty filters + pub fn enable_filtering(&mut self) { + self.filter = Some(HashSet::new()); + } + + /// Disables filtering + pub fn disable_filtering(&mut self) { + self.filter = None; + } } impl Default for LogDiagnosticsPlugin { @@ -52,7 +113,8 @@ impl Plugin for LogDiagnosticsPlugin { } impl LogDiagnosticsPlugin { - pub fn filtered(filter: Vec) -> Self { + /// Filter logging to only the paths in `filter`. + pub fn filtered(filter: HashSet) -> Self { LogDiagnosticsPlugin { filter: Some(filter), ..Default::default() @@ -65,7 +127,7 @@ impl LogDiagnosticsPlugin { mut callback: impl FnMut(&Diagnostic), ) { if let Some(filter) = &state.filter { - for path in filter { + for path in filter.iter() { if let Some(diagnostic) = diagnostics.get(path) { if diagnostic.is_enabled { callback(diagnostic); @@ -92,7 +154,7 @@ impl LogDiagnosticsPlugin { }; info!( - target: "bevy diagnostic", + target: "bevy_diagnostic", // Suffix is only used for 's' or 'ms' currently, // so we reserve two columns for it; however, // Do not reserve columns for the suffix in the average @@ -103,7 +165,7 @@ impl LogDiagnosticsPlugin { ); } else { info!( - target: "bevy diagnostic", + target: "bevy_diagnostic", "{path:.6}{suffix:}", path = diagnostic.path(), suffix = diagnostic.suffix, @@ -128,7 +190,7 @@ impl LogDiagnosticsPlugin { time: Res>, diagnostics: Res, ) { - if state.timer.tick(time.delta()).finished() { + if state.timer.tick(time.delta()).is_finished() { Self::log_diagnostics(&state, &diagnostics); } } @@ -138,7 +200,7 @@ impl LogDiagnosticsPlugin { time: Res>, diagnostics: Res, ) { - if state.timer.tick(time.delta()).finished() { + if state.timer.tick(time.delta()).is_finished() { Self::for_each_diagnostic(&state, &diagnostics, |diagnostic| { debug!("{:#?}\n", diagnostic); }); diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 5fef2a4f25..768bbb0828 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -46,10 +46,15 @@ impl SystemInformationDiagnosticsPlugin { /// [`SystemInformationDiagnosticsPlugin`] for more information. #[derive(Debug, Resource)] pub struct SystemInfo { + /// OS name and version. pub os: String, + /// System kernel version. pub kernel: String, + /// CPU model name. pub cpu: String, + /// Physical core count. pub core_count: String, + /// System RAM. pub memory: String, } @@ -74,7 +79,7 @@ pub mod internal { use bevy_app::{App, First, Startup, Update}; use bevy_ecs::resource::Resource; use bevy_ecs::{prelude::ResMut, system::Local}; - use bevy_platform_support::time::Instant; + use bevy_platform::time::Instant; use bevy_tasks::{available_parallelism, block_on, poll_once, AsyncComputeTaskPool, Task}; use log::info; use std::sync::Mutex; @@ -224,8 +229,7 @@ pub mod internal { .first() .map(|cpu| cpu.brand().trim().to_string()) .unwrap_or_else(|| String::from("not available")), - core_count: sys - .physical_core_count() + core_count: System::physical_core_count() .map(|x| x.to_string()) .unwrap_or_else(|| String::from("not available")), // Convert from Bytes to GibiBytes since it's probably what people expect most of the time diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 2b8d2f0d54..bf71d217d4 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["ecs", "game", "bevy"] categories = ["game-engines", "data-structures"] -rust-version = "1.85.0" +rust-version = "1.86.0" [features] default = ["std", "bevy_reflect", "async_executor", "backtrace"] @@ -20,12 +20,7 @@ default = ["std", "bevy_reflect", "async_executor", "backtrace"] multi_threaded = ["bevy_tasks/multi_threaded", "dep:arrayvec"] ## Adds serialization support through `serde`. -serialize = [ - "dep:serde", - "bevy_utils/serde", - "bevy_platform_support/serialize", - "indexmap/serde", -] +serialize = ["dep:serde", "bevy_platform/serialize", "indexmap/serde"] ## Adds runtime reflection support using `bevy_reflect`. bevy_reflect = ["dep:bevy_reflect"] @@ -33,9 +28,6 @@ bevy_reflect = ["dep:bevy_reflect"] ## Extends reflection support to functions. reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] -## Use the configurable global error handler as the default error handler -configurable_error_handler = [] - ## Enables automatic backtrace capturing in BevyError backtrace = ["std"] @@ -70,7 +62,7 @@ async_executor = ["std", "bevy_tasks/async_executor"] std = [ "bevy_reflect?/std", "bevy_tasks/std", - "bevy_utils/std", + "bevy_utils/parallel", "bitflags/std", "concurrent-queue/std", "disqualified/alloc", @@ -80,14 +72,14 @@ std = [ "nonmax/std", "arrayvec?/std", "log/std", - "bevy_platform_support/std", + "bevy_platform/std", ] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. critical-section = [ "bevy_tasks/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", "bevy_reflect?/critical-section", ] @@ -97,16 +89,13 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "smallvec", ], default-features = false, optional = true } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_ecs_macros = { path = "macros", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "alloc", ] } bitflags = { version = "2.3", default-features = false } -concurrent-queue = { version = "2.5.0", default-features = false } disqualified = { version = "1.0", default-features = false } fixedbitset = { version = "0.5", default-features = false } serde = { version = "1", default-features = false, features = [ @@ -129,6 +118,7 @@ tracing = { version = "0.1", default-features = false, optional = true } log = { version = "0.4", default-features = false } bumpalo = "3" +concurrent-queue = { version = "2.5.0", default-features = false } [target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies] concurrent-queue = { version = "2.5.0", default-features = false, features = [ "portable-atomic", diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 8614dbc5e3..c2fdc53d05 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -290,7 +290,7 @@ struct MyEvent { } fn writer(mut writer: EventWriter) { - writer.send(MyEvent { + writer.write(MyEvent { message: "hello!".to_string(), }); } diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 1b101b4033..42611e57e1 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -29,11 +29,11 @@ fn main() { // Add systems to the Schedule to execute our app logic // We can label our systems to force a specific run-order between some of them schedule.add_systems(( - spawn_entities.in_set(SimulationSet::Spawn), - print_counter_when_changed.after(SimulationSet::Spawn), - age_all_entities.in_set(SimulationSet::Age), - remove_old_entities.after(SimulationSet::Age), - print_changed_entities.after(SimulationSet::Age), + spawn_entities.in_set(SimulationSystems::Spawn), + print_counter_when_changed.after(SimulationSystems::Spawn), + age_all_entities.in_set(SimulationSystems::Age), + remove_old_entities.after(SimulationSystems::Age), + print_changed_entities.after(SimulationSystems::Age), )); // Simulate 10 frames in our world @@ -57,7 +57,7 @@ struct Age { // System sets can be used to group systems and configured to control relative ordering #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] -enum SimulationSet { +enum SimulationSystems { Spawn, Age, } @@ -84,7 +84,7 @@ fn print_changed_entities( entity_with_mutated_component: Query<(Entity, &Age), Changed>, ) { for entity in &entity_with_added_component { - println!(" {entity} has it's first birthday!"); + println!(" {entity} has its first birthday!"); } for (entity, value) in &entity_with_mutated_component { println!(" {entity} is now {value:?} frames old"); diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index 70efa9b471..fb01184048 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -1,5 +1,5 @@ //! In this example a system sends a custom event with a 50/50 chance during any frame. -//! If an event was send, it will be printed by the console in a receiving system. +//! If an event was sent, it will be printed by the console in a receiving system. #![expect(clippy::print_stdout, reason = "Allowed in examples.")] @@ -19,13 +19,13 @@ fn main() { // This update should happen before we use the events. // Here, we use system sets to control the ordering. #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] - pub struct FlushEvents; + pub struct EventFlusherSystems; - schedule.add_systems(bevy_ecs::event::event_update_system.in_set(FlushEvents)); + schedule.add_systems(bevy_ecs::event::event_update_system.in_set(EventFlusherSystems)); // Add systems sending and receiving events after the events are flushed. schedule.add_systems(( - sending_system.after(FlushEvents), + sending_system.after(EventFlusherSystems), receiving_system.after(sending_system), )); diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 464e4efabf..00268cb680 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -3,14 +3,14 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens}; use std::collections::HashSet; use syn::{ - parenthesized, + braced, parenthesized, parse::Parse, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, - token::{Comma, Paren}, - Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprClosure, ExprPath, Field, Fields, - Ident, LitStr, Member, Path, Result, Token, Type, Visibility, + token::{Brace, Comma, Paren}, + Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident, + LitStr, Member, Path, Result, Token, Type, Visibility, }; pub const EVENT: &str = "event"; @@ -38,7 +38,7 @@ pub fn derive_event(input: TokenStream) -> TokenStream { traversal = meta.value()?.parse()?; Ok(()) } - Some(ident) => Err(meta.error(format!("unsupported attribute: {}", ident))), + Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))), None => Err(meta.error("expected identifier")), }) { return e.to_compile_error().into(); @@ -92,12 +92,17 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Err(err) => err.into_compile_error().into(), }; - let visit_entities = visit_entities( + let map_entities = map_entities( &ast.data, - &bevy_ecs_path, + Ident::new("this", Span::call_site()), relationship.is_some(), relationship_target.is_some(), - ); + ).map(|map_entities_impl| quote! { + fn map_entities(this: &mut Self, mapper: &mut M) { + use #bevy_ecs_path::entity::MapEntities; + #map_entities_impl + } + }); let storage = storage_path(&bevy_ecs_path, attrs.storage); @@ -202,17 +207,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { ); }); match &require.func { - Some(RequireFunc::Path(func)) => { - register_required.push(quote! { - components.register_required_components_manual::( - required_components, - || { let x: #ident = #func().into(); x }, - inheritance_depth, - recursion_check_stack - ); - }); - } - Some(RequireFunc::Closure(func)) => { + Some(func) => { register_required.push(quote! { components.register_required_components_manual::( required_components, @@ -256,6 +251,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream { let clone_behavior = if relationship_target.is_some() { quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::)) + } else if let Some(behavior) = attrs.clone_behavior { + quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior) } else { quote!( use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone}; @@ -295,7 +292,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { #clone_behavior } - #visit_entities + #map_entities } #relationship @@ -306,19 +303,18 @@ pub fn derive_component(input: TokenStream) -> TokenStream { const ENTITIES: &str = "entities"; -fn visit_entities( +pub(crate) fn map_entities( data: &Data, - bevy_ecs_path: &Path, + self_ident: Ident, is_relationship: bool, is_relationship_target: bool, -) -> TokenStream2 { +) -> Option { match data { Data::Struct(DataStruct { fields, .. }) => { - let mut visit = Vec::with_capacity(fields.len()); - let mut visit_mut = Vec::with_capacity(fields.len()); + let mut map = Vec::with_capacity(fields.len()); let relationship = if is_relationship || is_relationship_target { - relationship_field(fields, "VisitEntities", fields.span()).ok() + relationship_field(fields, "MapEntities", fields.span()).ok() } else { None }; @@ -335,27 +331,17 @@ fn visit_entities( .clone() .map_or(Member::from(index), Member::Named); - visit.push(quote!(this.#field_member.visit_entities(&mut func);)); - visit_mut.push(quote!(this.#field_member.visit_entities_mut(&mut func);)); + map.push(quote!(#self_ident.#field_member.map_entities(mapper);)); }); - if visit.is_empty() { - return quote!(); + if map.is_empty() { + return None; }; - quote!( - fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) { - use #bevy_ecs_path::entity::VisitEntities; - #(#visit)* - } - - fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) { - use #bevy_ecs_path::entity::VisitEntitiesMut; - #(#visit_mut)* - } - ) + Some(quote!( + #(#map)* + )) } Data::Enum(DataEnum { variants, .. }) => { - let mut visit = Vec::with_capacity(variants.len()); - let mut visit_mut = Vec::with_capacity(variants.len()); + let mut map = Vec::with_capacity(variants.len()); for variant in variants.iter() { let field_members = variant @@ -377,40 +363,25 @@ fn visit_entities( .map(|member| format_ident!("__self_{}", member)) .collect::>(); - visit.push( + map.push( quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => { - #(#field_idents.visit_entities(&mut func);)* - }), - ); - visit_mut.push( - quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => { - #(#field_idents.visit_entities_mut(&mut func);)* + #(#field_idents.map_entities(mapper);)* }), ); } - if visit.is_empty() { - return quote!(); + if map.is_empty() { + return None; }; - quote!( - fn visit_entities(this: &Self, mut func: impl FnMut(#bevy_ecs_path::entity::Entity)) { - use #bevy_ecs_path::entity::VisitEntities; - match this { - #(#visit,)* - _ => {} - } - } - fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut #bevy_ecs_path::entity::Entity)) { - use #bevy_ecs_path::entity::VisitEntitiesMut; - match this { - #(#visit_mut,)* - _ => {} - } + Some(quote!( + match #self_ident { + #(#map,)* + _ => {} } - ) + )) } - Data::Union(_) => quote!(), + Data::Union(_) => None, } } @@ -427,6 +398,7 @@ pub const ON_REMOVE: &str = "on_remove"; pub const ON_DESPAWN: &str = "on_despawn"; pub const IMMUTABLE: &str = "immutable"; +pub const CLONE_BEHAVIOR: &str = "clone_behavior"; /// All allowed attribute value expression kinds for component hooks #[derive(Debug)] @@ -489,6 +461,7 @@ struct Attrs { relationship: Option, relationship_target: Option, immutable: bool, + clone_behavior: Option, } #[derive(Clone, Copy)] @@ -499,12 +472,7 @@ enum StorageTy { struct Require { path: Path, - func: Option, -} - -enum RequireFunc { - Path(Path), - Closure(ExprClosure), + func: Option, } struct Relationship { @@ -532,6 +500,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { relationship: None, relationship_target: None, immutable: false, + clone_behavior: None, }; let mut require_paths = HashSet::new(); @@ -567,6 +536,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(IMMUTABLE) { attrs.immutable = true; Ok(()) + } else if nested.path.is_ident(CLONE_BEHAVIOR) { + attrs.clone_behavior = Some(nested.value()?.parse()?); + Ok(()) } else { Err(nested.error("Unsupported attribute")) } @@ -596,24 +568,76 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } } + if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() { + return Err(syn::Error::new( + attrs.clone_behavior.span(), + "A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`", + )); + } + Ok(attrs) } impl Parse for Require { fn parse(input: syn::parse::ParseStream) -> Result { - let path = input.parse::()?; - let func = if input.peek(Paren) { + let mut path = input.parse::()?; + let mut last_segment_is_lower = false; + let mut is_constructor_call = false; + + // Use the case of the type name to check if it's an enum + // This doesn't match everything that can be an enum according to the rust spec + // but it matches what clippy is OK with + let is_enum = { + let mut first_chars = path + .segments + .iter() + .rev() + .filter_map(|s| s.ident.to_string().chars().next()); + if let Some(last) = first_chars.next() { + if last.is_uppercase() { + if let Some(last) = first_chars.next() { + last.is_uppercase() + } else { + false + } + } else { + last_segment_is_lower = true; + false + } + } else { + false + } + }; + + let func = if input.peek(Token![=]) { + // If there is an '=', then this is a "function style" require + input.parse::()?; + let expr: Expr = input.parse()?; + Some(quote!(|| #expr )) + } else if input.peek(Brace) { + // This is a "value style" named-struct-like require + let content; + braced!(content in input); + let content = content.parse::()?; + Some(quote!(|| #path { #content })) + } else if input.peek(Paren) { + // This is a "value style" tuple-struct-like require let content; parenthesized!(content in input); - if let Ok(func) = content.parse::() { - Some(RequireFunc::Closure(func)) - } else { - let func = content.parse::()?; - Some(RequireFunc::Path(func)) - } + let content = content.parse::()?; + is_constructor_call = last_segment_is_lower; + Some(quote!(|| #path (#content))) + } else if is_enum { + // if this is an enum, then it is an inline enum component declaration + Some(quote!(|| #path)) } else { + // if this isn't any of the above, then it is a component ident, which will use Default None }; + if is_enum || is_constructor_call { + path.segments.pop(); + path.segments.pop_punct(); + } Ok(Require { path, func }) } } @@ -730,7 +754,7 @@ fn derive_relationship( #[inline] fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { Self { - #(#members: core::default::Default::default(),),* + #(#members: core::default::Default::default(),)* #relationship_member: entity } } @@ -793,7 +817,7 @@ fn derive_relationship_target( #[inline] fn from_collection_risky(collection: Self::Collection) -> Self { Self { - #(#members: core::default::Default::default(),),* + #(#members: core::default::Default::default(),)* #relationship_member: collection } } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index afe56c23e0..114aff642b 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -6,13 +6,15 @@ extern crate proc_macro; mod component; mod query_data; mod query_filter; -mod states; mod world_query; -use crate::{query_data::derive_query_data_impl, query_filter::derive_query_filter_impl}; +use crate::{ + component::map_entities, query_data::derive_query_data_impl, + query_filter::derive_query_filter_impl, +}; use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, @@ -32,7 +34,7 @@ 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_struct_fields(&ast.data) { + let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") { Ok(fields) => fields, Err(e) => return e.into_compile_error().into(), }; @@ -185,105 +187,24 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { }) } -fn derive_visit_entities_base( - input: TokenStream, - trait_name: TokenStream2, - gen_methods: impl FnOnce(Vec) -> TokenStream2, -) -> TokenStream { +#[proc_macro_derive(MapEntities, attributes(entities))] +pub fn derive_map_entities(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let ecs_path = bevy_ecs_path(); - let named_fields = match get_struct_fields(&ast.data) { - Ok(fields) => fields, - Err(e) => return e.into_compile_error().into(), - }; + let map_entities_impl = map_entities( + &ast.data, + Ident::new("self", Span::call_site()), + false, + false, + ); - let field = named_fields - .iter() - .filter_map(|field| { - if let Some(attr) = field - .attrs - .iter() - .find(|a| a.path().is_ident("visit_entities")) - { - let ignore = attr.parse_nested_meta(|meta| { - if meta.path.is_ident("ignore") { - Ok(()) - } else { - Err(meta.error("Invalid visit_entities attribute. Use `ignore`")) - } - }); - return match ignore { - Ok(()) => None, - Err(e) => Some(Err(e)), - }; - } - Some(Ok(field)) - }) - .map(|res| res.map(|field| field.ident.as_ref())) - .collect::, _>>(); - - let field = match field { - Ok(field) => field, - Err(e) => return e.into_compile_error().into(), - }; - - if field.is_empty() { - return syn::Error::new( - ast.span(), - format!("Invalid `{}` type: at least one field", trait_name), - ) - .into_compile_error() - .into(); - } - - let field_access = field - .iter() - .enumerate() - .map(|(n, f)| { - if let Some(ident) = f { - quote! { - self.#ident - } - } else { - let idx = Index::from(n); - quote! { - self.#idx - } - } - }) - .collect::>(); - - let methods = gen_methods(field_access); - - let generics = ast.generics; - let (impl_generics, ty_generics, _) = generics.split_for_impl(); let struct_name = &ast.ident; - + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); TokenStream::from(quote! { - impl #impl_generics #ecs_path::entity:: #trait_name for #struct_name #ty_generics { - #methods - } - }) -} - -#[proc_macro_derive(VisitEntitiesMut, attributes(visit_entities))] -pub fn derive_visit_entities_mut(input: TokenStream) -> TokenStream { - derive_visit_entities_base(input, quote! { VisitEntitiesMut }, |field| { - quote! { - fn visit_entities_mut(&mut self, mut f: F) { - #(#field.visit_entities_mut(&mut f);)* - } - } - }) -} - -#[proc_macro_derive(VisitEntities, attributes(visit_entities))] -pub fn derive_visit_entities(input: TokenStream) -> TokenStream { - derive_visit_entities_base(input, quote! { VisitEntities }, |field| { - quote! { - fn visit_entities(&self, mut f: F) { - #(#field.visit_entities(&mut f);)* + impl #impl_generics #ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause { + fn map_entities(&mut self, mapper: &mut M) { + #map_entities_impl } } }) @@ -309,19 +230,39 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { let path = bevy_ecs_path(); let mut field_locals = Vec::new(); + let mut field_names = Vec::new(); let mut fields = Vec::new(); let mut field_types = Vec::new(); + let mut field_messages = Vec::new(); for (i, field) in field_definitions.iter().enumerate() { field_locals.push(format_ident!("f{i}")); let i = Index::from(i); - fields.push( - field - .ident - .as_ref() - .map(|f| quote! { #f }) - .unwrap_or_else(|| quote! { #i }), - ); + let field_value = field + .ident + .as_ref() + .map(|f| quote! { #f }) + .unwrap_or_else(|| quote! { #i }); + field_names.push(format!("::{field_value}")); + fields.push(field_value); field_types.push(&field.ty); + let mut field_message = None; + for meta in field + .attrs + .iter() + .filter(|a| a.path().is_ident("system_param")) + { + if let Err(e) = meta.parse_nested_meta(|nested| { + if nested.path.is_ident("validation_message") { + field_message = Some(nested.value()?.parse()?); + Ok(()) + } else { + Err(nested.error("Unsupported attribute")) + } + }) { + return e.into_compile_error().into(); + } + } + field_messages.push(field_message.unwrap_or_else(|| quote! { err.message })); } let generics = ast.generics; @@ -491,11 +432,6 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { } } - unsafe fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) } - } - fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world); } @@ -506,11 +442,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { #[inline] unsafe fn validate_param<'w, 's>( - state: &'s Self::State, - system_meta: &#path::system::SystemMeta, - world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>, - ) -> bool { - <(#(#tuple_types,)*) as #path::system::SystemParam>::validate_param(&state.state, system_meta, world) + state: &'s mut Self::State, + _system_meta: &#path::system::SystemMeta, + _world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>, + ) -> Result<(), #path::system::SystemParamValidationError> { + let #state_struct_name { state: (#(#tuple_patterns,)*) } = state; + #( + <#field_types as #path::system::SystemParam>::validate_param(#field_locals, _system_meta, _world) + .map_err(|err| #path::system::SystemParamValidationError::new::(err.skipped, #field_messages, #field_names))?; + )* + Result::Ok(()) } #[inline] @@ -559,12 +500,10 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); - let mut dyn_eq_path = trait_path.clone(); trait_path .segments .push(format_ident!("ScheduleLabel").into()); - dyn_eq_path.segments.push(format_ident!("DynEq").into()); - derive_label(input, "ScheduleLabel", &trait_path, &dyn_eq_path) + derive_label(input, "ScheduleLabel", &trait_path) } /// Derive macro generating an impl of the trait `SystemSet`. @@ -575,10 +514,8 @@ pub fn derive_system_set(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); trait_path.segments.push(format_ident!("schedule").into()); - let mut dyn_eq_path = trait_path.clone(); trait_path.segments.push(format_ident!("SystemSet").into()); - dyn_eq_path.segments.push(format_ident!("DynEq").into()); - derive_label(input, "SystemSet", &trait_path, &dyn_eq_path) + derive_label(input, "SystemSet", &trait_path) } pub(crate) fn bevy_ecs_path() -> syn::Path { @@ -603,16 +540,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } -#[proc_macro_derive(States)] -pub fn derive_states(input: TokenStream) -> TokenStream { - states::derive_states(input) -} - -#[proc_macro_derive(SubStates, attributes(source))] -pub fn derive_substates(input: TokenStream) -> TokenStream { - states::derive_substates(input) -} - #[proc_macro_derive(FromWorld, attributes(from_world))] pub fn derive_from_world(input: TokenStream) -> TokenStream { let bevy_ecs_path = bevy_ecs_path(); diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index d919d0b05e..4e4529e631 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -268,6 +268,14 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { } } + fn provide_extra_access( + state: &mut Self::State, + access: &mut #path::query::Access<#path::component::ComponentId>, + available_access: &#path::query::Access<#path::component::ComponentId>, + ) { + #(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)* + } + /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] unsafe fn fetch<'__w>( @@ -305,6 +313,14 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { } } + fn provide_extra_access( + state: &mut Self::State, + access: &mut #path::query::Access<#path::component::ComponentId>, + available_access: &#path::query::Access<#path::component::ComponentId>, + ) { + #(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)* + } + /// SAFETY: we call `fetch` for each member that implements `Fetch`. #[inline(always)] unsafe fn fetch<'__w>( diff --git a/crates/bevy_ecs/macros/src/states.rs b/crates/bevy_ecs/macros/src/states.rs deleted file mode 100644 index ff69812aea..0000000000 --- a/crates/bevy_ecs/macros/src/states.rs +++ /dev/null @@ -1,144 +0,0 @@ -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result}; - -use crate::bevy_ecs_path; - -pub fn derive_states(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - - let generics = ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let mut base_trait_path = bevy_ecs_path(); - base_trait_path - .segments - .push(format_ident!("schedule").into()); - - let mut trait_path = base_trait_path.clone(); - trait_path.segments.push(format_ident!("States").into()); - - let mut state_mutation_trait_path = base_trait_path.clone(); - state_mutation_trait_path - .segments - .push(format_ident!("FreelyMutableState").into()); - - let struct_name = &ast.ident; - - quote! { - impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {} - - impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause { - } - } - .into() -} - -struct Source { - source_type: Path, - source_value: Pat, -} - -fn parse_sources_attr(ast: &DeriveInput) -> Result { - let mut result = ast - .attrs - .iter() - .filter(|a| a.path().is_ident("source")) - .map(|meta| { - let mut source = None; - let value = meta.parse_nested_meta(|nested| { - let source_type = nested.path.clone(); - let source_value = Pat::parse_multi(nested.value()?)?; - source = Some(Source { - source_type, - source_value, - }); - Ok(()) - }); - match source { - Some(value) => Ok(value), - None => match value { - Ok(_) => Err(syn::Error::new( - ast.span(), - "Couldn't parse SubStates source", - )), - Err(e) => Err(e), - }, - } - }) - .collect::>>()?; - - if result.len() > 1 { - return Err(syn::Error::new( - ast.span(), - "Only one source is allowed for SubStates", - )); - } - - let Some(result) = result.pop() else { - return Err(syn::Error::new(ast.span(), "SubStates require a source")); - }; - - Ok(result) -} - -pub fn derive_substates(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let sources = parse_sources_attr(&ast).expect("Failed to parse substate sources"); - - let generics = ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let mut base_trait_path = bevy_ecs_path(); - base_trait_path - .segments - .push(format_ident!("schedule").into()); - - let mut trait_path = base_trait_path.clone(); - trait_path.segments.push(format_ident!("SubStates").into()); - - let mut state_set_trait_path = base_trait_path.clone(); - state_set_trait_path - .segments - .push(format_ident!("StateSet").into()); - - let mut state_trait_path = base_trait_path.clone(); - state_trait_path - .segments - .push(format_ident!("States").into()); - - let mut state_mutation_trait_path = base_trait_path.clone(); - state_mutation_trait_path - .segments - .push(format_ident!("FreelyMutableState").into()); - - let struct_name = &ast.ident; - - let source_state_type = sources.source_type; - let source_state_value = sources.source_value; - - let result = quote! { - impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause { - type SourceStates = #source_state_type; - - fn should_exist(sources: #source_state_type) -> Option { - if matches!(sources, #source_state_value) { - Some(Self::default()) - } else { - None - } - } - } - - impl #impl_generics #state_trait_path for #struct_name #ty_generics #where_clause { - const DEPENDENCY_DEPTH : usize = ::SourceStates::SET_DEPENDENCY_DEPTH + 1; - } - - impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause { - } - }; - - // panic!("Got Result\n{}", result.to_string()); - - result.into() -} diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index 77ee532a50..5c4c0bff01 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -92,7 +92,7 @@ pub(crate) fn world_query_impl( } } - // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field + // SAFETY: `update_component_access` is called on every field unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses { diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e6ae5e4ae3..f12cd03a69 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -24,14 +24,15 @@ use crate::{ component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, observer::Observers, - storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, + storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::{ hash::Hash, ops::{Index, IndexMut, RangeFrom}, }; +use nonmax::NonMaxU32; /// An opaque location within a [`Archetype`]. /// @@ -44,23 +45,30 @@ use core::{ #[derive(Debug, Copy, Clone, Eq, PartialEq)] // SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation #[repr(transparent)] -pub struct ArchetypeRow(u32); +pub struct ArchetypeRow(NonMaxU32); impl ArchetypeRow { /// Index indicating an invalid archetype row. /// This is meant to be used as a placeholder. - pub const INVALID: ArchetypeRow = ArchetypeRow(u32::MAX); + // TODO: Deprecate in favor of options, since `INVALID` is, technically, valid. + pub const INVALID: ArchetypeRow = ArchetypeRow(NonMaxU32::MAX); /// Creates a `ArchetypeRow`. #[inline] - pub const fn new(index: usize) -> Self { - Self(index as u32) + pub const fn new(index: NonMaxU32) -> Self { + Self(index) } /// Gets the index of the row. #[inline] pub const fn index(self) -> usize { - self.0 as usize + self.0.get() as usize + } + + /// Gets the index of the row. + #[inline] + pub const fn index_u32(self) -> u32 { + self.0.get() } } @@ -341,7 +349,6 @@ pub(crate) struct ArchetypeSwapRemoveResult { /// [`Component`]: crate::component::Component struct ArchetypeComponentInfo { storage_type: StorageType, - archetype_component_id: ArchetypeComponentId, } bitflags::bitflags! { @@ -386,14 +393,14 @@ impl Archetype { observers: &Observers, id: ArchetypeId, table_id: TableId, - table_components: impl Iterator, - sparse_set_components: impl Iterator, + table_components: impl Iterator, + sparse_set_components: impl Iterator, ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); let mut flags = ArchetypeFlags::empty(); let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); - for (idx, (component_id, archetype_component_id)) in table_components.enumerate() { + for (idx, component_id) in table_components.enumerate() { // SAFETY: We are creating an archetype that includes this component so it must exist let info = unsafe { components.get_info_unchecked(component_id) }; info.update_archetype_flags(&mut flags); @@ -402,7 +409,6 @@ impl Archetype { component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, - archetype_component_id, }, ); // NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same @@ -414,7 +420,7 @@ impl Archetype { .insert(id, ArchetypeRecord { column: Some(idx) }); } - for (component_id, archetype_component_id) in sparse_set_components { + for component_id in sparse_set_components { // SAFETY: We are creating an archetype that includes this component so it must exist let info = unsafe { components.get_info_unchecked(component_id) }; info.update_archetype_flags(&mut flags); @@ -423,7 +429,6 @@ impl Archetype { component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, - archetype_component_id, }, ); component_index @@ -467,6 +472,27 @@ impl Archetype { &self.entities } + /// Fetches the entities contained in this archetype. + #[inline] + pub fn entities_with_location(&self) -> impl Iterator { + self.entities.iter().enumerate().map( + |(archetype_row, &ArchetypeEntity { entity, table_row })| { + ( + entity, + EntityLocation { + archetype_id: self.id, + // SAFETY: The entities in the archetype must be unique and there are never more than u32::MAX entities. + archetype_row: unsafe { + ArchetypeRow::new(NonMaxU32::new_unchecked(archetype_row as u32)) + }, + table_id: self.table_id, + table_row, + }, + ) + }, + ) + } + /// Gets an iterator of all of the components stored in [`Table`]s. /// /// All of the IDs are unique. @@ -507,16 +533,6 @@ impl Archetype { self.components.len() } - /// Gets an iterator of all of the components in the archetype, along with - /// their archetype component ID. - pub(crate) fn components_with_archetype_component_id( - &self, - ) -> impl Iterator + '_ { - self.components - .iter() - .map(|(component_id, info)| (*component_id, info.archetype_component_id)) - } - /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] @@ -569,7 +585,8 @@ impl Archetype { entity: Entity, table_row: TableRow, ) -> EntityLocation { - let archetype_row = ArchetypeRow::new(self.entities.len()); + // SAFETY: An entity can not have multiple archetype rows and there can not be more than u32::MAX entities. + let archetype_row = unsafe { ArchetypeRow::new(NonMaxU32::new_unchecked(self.len())) }; self.entities.push(ArchetypeEntity { entity, table_row }); EntityLocation { @@ -606,8 +623,10 @@ impl Archetype { /// Gets the total number of entities that belong to the archetype. #[inline] - pub fn len(&self) -> usize { - self.entities.len() + pub fn len(&self) -> u32 { + // No entity may have more than one archetype row, so there are no duplicates, + // and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity. + self.entities.len() as u32 } /// Checks if the archetype has any entities. @@ -632,19 +651,6 @@ impl Archetype { .map(|info| info.storage_type) } - /// Fetches the corresponding [`ArchetypeComponentId`] for a component in the archetype. - /// Returns `None` if the component is not part of the archetype. - /// This runs in `O(1)` time. - #[inline] - pub fn get_archetype_component_id( - &self, - component_id: ComponentId, - ) -> Option { - self.components - .get(component_id) - .map(|info| info.archetype_component_id) - } - /// Clears all entities from the archetype. pub(crate) fn clear_entities(&mut self) { self.entities.clear(); @@ -742,46 +748,6 @@ struct ArchetypeComponents { sparse_set_components: Box<[ComponentId]>, } -/// An opaque unique joint ID for a [`Component`] in an [`Archetype`] within a [`World`]. -/// -/// A component may be present within multiple archetypes, but each component within -/// each archetype has its own unique `ArchetypeComponentId`. This is leveraged by the system -/// schedulers to opportunistically run multiple systems in parallel that would otherwise -/// conflict. For example, `Query<&mut A, With>` and `Query<&mut A, Without>` can run in -/// parallel as the matched `ArchetypeComponentId` sets for both queries are disjoint, even -/// though `&mut A` on both queries point to the same [`ComponentId`]. -/// -/// In SQL terms, these IDs are composite keys on a [many-to-many relationship] between archetypes -/// and components. Each component type will have only one [`ComponentId`], but may have many -/// [`ArchetypeComponentId`]s, one for every archetype the component is present in. Likewise, each -/// archetype will have only one [`ArchetypeId`] but may have many [`ArchetypeComponentId`]s, one -/// for each component that belongs to the archetype. -/// -/// Every [`Resource`] is also assigned one of these IDs. As resources do not belong to any -/// particular archetype, a resource's ID uniquely identifies it. -/// -/// These IDs are only valid within a given World, and are not globally unique. -/// Attempting to use an ID on a world that it wasn't sourced from will -/// not point to the same archetype nor the same component. -/// -/// [`Component`]: crate::component::Component -/// [`World`]: crate::world::World -/// [`Resource`]: crate::resource::Resource -/// [many-to-many relationship]: https://en.wikipedia.org/wiki/Many-to-many_(data_model) -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct ArchetypeComponentId(usize); - -impl SparseSetIndex for ArchetypeComponentId { - #[inline] - fn sparse_set_index(&self) -> usize { - self.0 - } - - fn get_sparse_set_index(value: usize) -> Self { - Self(value) - } -} - /// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component), /// along with an [`ArchetypeRecord`] which contains some metadata about how the component is stored in the archetype. pub type ComponentIndex = HashMap>; @@ -794,7 +760,6 @@ pub type ComponentIndex = HashMap, - archetype_component_count: usize, /// find the archetype id by the archetype's components by_components: HashMap, /// find all the archetypes that contain a component @@ -818,7 +783,6 @@ impl Archetypes { archetypes: Vec::new(), by_components: Default::default(), by_component: Default::default(), - archetype_component_count: 0, }; // SAFETY: Empty archetype has no components unsafe { @@ -873,22 +837,6 @@ impl Archetypes { } } - /// Generate and store a new [`ArchetypeComponentId`]. - /// - /// This simply increment the counter and return the new value. - /// - /// # Panics - /// - /// On archetype component id overflow. - pub(crate) fn new_archetype_component_id(&mut self) -> ArchetypeComponentId { - let id = ArchetypeComponentId(self.archetype_component_count); - self.archetype_component_count = self - .archetype_component_count - .checked_add(1) - .expect("archetype_component_count overflow"); - id - } - /// Fetches an immutable reference to an [`Archetype`] using its /// ID. Returns `None` if no corresponding archetype exists. #[inline] @@ -940,9 +888,8 @@ impl Archetypes { }; let archetypes = &mut self.archetypes; - let archetype_component_count = &mut self.archetype_component_count; let component_index = &mut self.by_component; - let archetype_id = *self + *self .by_components .entry(archetype_identity) .or_insert_with_key(move |identity| { @@ -951,39 +898,17 @@ impl Archetypes { sparse_set_components, } = identity; let id = ArchetypeId::new(archetypes.len()); - let table_start = *archetype_component_count; - *archetype_component_count += table_components.len(); - let table_archetype_components = - (table_start..*archetype_component_count).map(ArchetypeComponentId); - let sparse_start = *archetype_component_count; - *archetype_component_count += sparse_set_components.len(); - let sparse_set_archetype_components = - (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( components, component_index, observers, id, table_id, - table_components - .iter() - .copied() - .zip(table_archetype_components), - sparse_set_components - .iter() - .copied() - .zip(sparse_set_archetype_components), + table_components.iter().copied(), + sparse_set_components.iter().copied(), )); id - }); - archetype_id - } - - /// Returns the number of components that are stored in archetypes. - /// Note that if some component `T` is stored in more than one archetype, it will be counted once for each archetype it's present in. - #[inline] - pub fn archetype_components_len(&self) -> usize { - self.archetype_component_count + }) } /// Clears all entities from all archetypes. diff --git a/crates/bevy_ecs/src/batching.rs b/crates/bevy_ecs/src/batching.rs index cdffbfa05d..ab9f2d582c 100644 --- a/crates/bevy_ecs/src/batching.rs +++ b/crates/bevy_ecs/src/batching.rs @@ -22,7 +22,7 @@ use core::ops::Range; /// [`EventReader::par_read`]: crate::event::EventReader::par_read #[derive(Clone, Debug)] pub struct BatchingStrategy { - /// The upper and lower limits for a batch of entities. + /// The upper and lower limits for a batch of items. /// /// Setting the bounds to the same value will result in a fixed /// batch size. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5946819032..e3e54c092f 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -20,10 +20,13 @@ use crate::{ query::DebugCheckedUnwrap, relationship::RelationshipHookMode, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE}, + world::{ + unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE, + ON_REPLACE, + }, }; use alloc::{boxed::Box, vec, vec::Vec}; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::TypeIdMap; use core::{any::TypeId, ptr::NonNull}; @@ -630,13 +633,14 @@ impl BundleInfo { let mut bundle_component = 0; let after_effect = bundle.get_components(&mut |storage_type, component_ptr| { let component_id = *self.component_ids.get_unchecked(bundle_component); + // SAFETY: bundle_component is a valid index for this bundle + let status = unsafe { bundle_component_status.get_status(bundle_component) }; match storage_type { StorageType::Table => { - // SAFETY: bundle_component is a valid index for this bundle - let status = unsafe { bundle_component_status.get_status(bundle_component) }; - // SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that - // the target table contains the component. - let column = table.get_column_mut(component_id).debug_checked_unwrap(); + let column = + // SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that + // the target table contains the component. + unsafe { table.get_column_mut(component_id).debug_checked_unwrap() }; match (status, insert_mode) { (ComponentStatus::Added, _) => { column.initialize(table_row, component_ptr, change_tick, caller); @@ -656,7 +660,16 @@ impl BundleInfo { // SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that // a sparse set exists for the component. unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() }; - sparse_set.insert(entity, component_ptr, change_tick, caller); + match (status, insert_mode) { + (ComponentStatus::Added, _) | (_, InsertMode::Replace) => { + sparse_set.insert(entity, component_ptr, change_tick, caller); + } + (ComponentStatus::Existing, InsertMode::Keep) => { + if let Some(drop_fn) = sparse_set.get_drop() { + drop_fn(component_ptr); + } + } + } } } bundle_component += 1; @@ -1361,6 +1374,274 @@ impl<'w> BundleInserter<'w> { } } +// SAFETY: We have exclusive world access so our pointers can't be invalidated externally +pub(crate) struct BundleRemover<'w> { + world: UnsafeWorldCell<'w>, + bundle_info: ConstNonNull, + old_and_new_table: Option<(NonNull
, NonNull
)>, + old_archetype: NonNull, + new_archetype: NonNull, +} + +impl<'w> BundleRemover<'w> { + /// Creates a new [`BundleRemover`], if such a remover would do anything. + /// + /// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype. + /// + /// # Safety + /// Caller must ensure that `archetype_id` is valid + #[inline] + pub(crate) unsafe fn new( + world: &'w mut World, + archetype_id: ArchetypeId, + require_all: bool, + ) -> Option { + // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. + let mut registrator = + unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) }; + let bundle_id = world + .bundles + .register_info::(&mut registrator, &mut world.storages); + // SAFETY: we initialized this bundle_id in `init_info`, and caller ensures archetype is valid. + unsafe { Self::new_with_id(world, archetype_id, bundle_id, require_all) } + } + + /// Creates a new [`BundleRemover`], if such a remover would do anything. + /// + /// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype. + /// + /// # Safety + /// Caller must ensure that `bundle_id` exists in `world.bundles` and `archetype_id` is valid. + #[inline] + pub(crate) unsafe fn new_with_id( + world: &'w mut World, + archetype_id: ArchetypeId, + bundle_id: BundleId, + require_all: bool, + ) -> Option { + let bundle_info = world.bundles.get_unchecked(bundle_id); + // SAFETY: Caller ensures archetype and bundle ids are correct. + let new_archetype_id = unsafe { + bundle_info.remove_bundle_from_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + &world.observers, + archetype_id, + !require_all, + )? + }; + if new_archetype_id == archetype_id { + return None; + } + let (old_archetype, new_archetype) = + world.archetypes.get_2_mut(archetype_id, new_archetype_id); + + let tables = if old_archetype.table_id() == new_archetype.table_id() { + None + } else { + let (old, new) = world + .storages + .tables + .get_2_mut(old_archetype.table_id(), new_archetype.table_id()); + Some((old.into(), new.into())) + }; + + Some(Self { + bundle_info: bundle_info.into(), + new_archetype: new_archetype.into(), + old_archetype: old_archetype.into(), + old_and_new_table: tables, + world: world.as_unsafe_world_cell(), + }) + } + + /// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing. + pub fn empty_pre_remove( + _: &mut SparseSets, + _: Option<&mut Table>, + _: &Components, + _: &[ComponentId], + ) -> (bool, ()) { + (true, ()) + } + + /// Performs the removal. + /// + /// `pre_remove` should return a bool for if the components still need to be dropped. + /// + /// # Safety + /// The `location` must have the same archetype as the remover. + #[inline] + pub(crate) unsafe fn remove( + &mut self, + entity: Entity, + location: EntityLocation, + caller: MaybeLocation, + pre_remove: impl FnOnce( + &mut SparseSets, + Option<&mut Table>, + &Components, + &[ComponentId], + ) -> (bool, T), + ) -> (EntityLocation, T) { + // Hooks + // SAFETY: all bundle components exist in World + unsafe { + // SAFETY: We only keep access to archetype/bundle data. + let mut deferred_world = self.world.into_deferred(); + let bundle_components_in_archetype = || { + self.bundle_info + .as_ref() + .iter_explicit_components() + .filter(|component_id| self.old_archetype.as_ref().contains(*component_id)) + }; + if self.old_archetype.as_ref().has_replace_observer() { + deferred_world.trigger_observers( + ON_REPLACE, + entity, + bundle_components_in_archetype(), + caller, + ); + } + deferred_world.trigger_on_replace( + self.old_archetype.as_ref(), + entity, + bundle_components_in_archetype(), + caller, + RelationshipHookMode::Run, + ); + if self.old_archetype.as_ref().has_remove_observer() { + deferred_world.trigger_observers( + ON_REMOVE, + entity, + bundle_components_in_archetype(), + caller, + ); + } + deferred_world.trigger_on_remove( + self.old_archetype.as_ref(), + entity, + bundle_components_in_archetype(), + caller, + ); + } + + // SAFETY: We still have the cell, so this is unique, it doesn't conflict with other references, and we drop it shortly. + let world = unsafe { self.world.world_mut() }; + + let (needs_drop, pre_remove_result) = pre_remove( + &mut world.storages.sparse_sets, + self.old_and_new_table + .as_ref() + // SAFETY: There is no conflicting access for this scope. + .map(|(old, _)| unsafe { &mut *old.as_ptr() }), + &world.components, + self.bundle_info.as_ref().explicit_components(), + ); + + // Handle sparse set removes + for component_id in self.bundle_info.as_ref().iter_explicit_components() { + if self.old_archetype.as_ref().contains(component_id) { + world.removed_components.send(component_id, entity); + + // Make sure to drop components stored in sparse sets. + // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. + if let Some(StorageType::SparseSet) = + self.old_archetype.as_ref().get_storage_type(component_id) + { + world + .storages + .sparse_sets + .get_mut(component_id) + // Set exists because the component existed on the entity + .unwrap() + // If it was already forgotten, it would not be in the set. + .remove(entity); + } + } + } + + // Handle archetype change + let remove_result = self + .old_archetype + .as_mut() + .swap_remove(location.archetype_row); + // if an entity was moved into this entity's archetype row, update its archetype row + if let Some(swapped_entity) = remove_result.swapped_entity { + let swapped_location = world.entities.get(swapped_entity).unwrap(); + + world.entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + + // Handle table change + let new_location = if let Some((mut old_table, mut new_table)) = self.old_and_new_table { + let move_result = if needs_drop { + // SAFETY: old_table_row exists + unsafe { + old_table + .as_mut() + .move_to_and_drop_missing_unchecked(location.table_row, new_table.as_mut()) + } + } else { + // SAFETY: old_table_row exists + unsafe { + old_table.as_mut().move_to_and_forget_missing_unchecked( + location.table_row, + new_table.as_mut(), + ) + } + }; + + // SAFETY: move_result.new_row is a valid position in new_archetype's table + let new_location = unsafe { + self.new_archetype + .as_mut() + .allocate(entity, move_result.new_row) + }; + + // if an entity was moved into this entity's table row, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = world.entities.get(swapped_entity).unwrap(); + + world.entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: swapped_location.archetype_row, + table_id: swapped_location.table_id, + table_row: location.table_row, + }, + ); + world.archetypes[swapped_location.archetype_id] + .set_entity_table_row(swapped_location.archetype_row, location.table_row); + } + + new_location + } else { + // The tables are the same + self.new_archetype + .as_mut() + .allocate(entity, location.table_row) + }; + + // SAFETY: The entity is valid and has been moved to the new location already. + unsafe { + world.entities.set(entity.index(), new_location); + } + + (new_location, pre_remove_result) + } +} + // SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { world: UnsafeWorldCell<'w>, @@ -1456,6 +1737,7 @@ impl<'w> BundleSpawner<'w> { caller, ); entities.set(entity.index(), location); + entities.mark_spawn_despawn(entity.index(), caller, self.change_tick); (location, after_effect) }; @@ -1585,7 +1867,7 @@ impl Bundles { storages: &mut Storages, ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids= Vec::new(); T::component_ids(components, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); @@ -1597,8 +1879,7 @@ impl Bundles { unsafe { BundleInfo::new(core::any::type_name::(), storages, components, component_ids, id) }; bundle_infos.push(bundle_info); id - }); - id + }) } /// Registers a new [`BundleInfo`], which contains both explicit and required components for a statically known type. @@ -1957,6 +2238,28 @@ mod tests { assert_eq!(entity.get(), Some(&V("one"))); } + #[derive(Component, Debug, Eq, PartialEq)] + #[component(storage = "SparseSet")] + pub struct SparseV(&'static str); + + #[derive(Component, Debug, Eq, PartialEq)] + #[component(storage = "SparseSet")] + pub struct SparseA; + + #[test] + fn sparse_set_insert_if_new() { + let mut world = World::new(); + let id = world.spawn(SparseV("one")).id(); + let mut entity = world.entity_mut(id); + entity.insert_if_new(SparseV("two")); + entity.insert_if_new((SparseA, SparseV("three"))); + entity.flush(); + // should still contain "one" + let entity = world.entity(id); + assert!(entity.contains::()); + assert_eq!(entity.get(), Some(&SparseV("one"))); + } + #[test] fn sorted_remove() { let mut a = vec![1, 2, 3, 4, 5, 6, 7]; diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index c4abfdc77a..85219d44ca 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -71,6 +71,9 @@ pub trait DetectChanges { /// [`SystemParam`](crate::system::SystemParam). fn last_changed(&self) -> Tick; + /// Returns the change tick recording the time this data was added. + fn added(&self) -> Tick; + /// The location that last caused this to change. fn changed_by(&self) -> MaybeLocation; } @@ -118,6 +121,15 @@ pub trait DetectChangesMut: DetectChanges { /// **Note**: This operation cannot be undone. fn set_changed(&mut self); + /// Flags this value as having been added. + /// + /// It is not normally necessary to call this method. + /// The 'added' tick is set when the value is first added, + /// and is not normally changed afterwards. + /// + /// **Note**: This operation cannot be undone. + fn set_added(&mut self); + /// Manually sets the change tick recording the time when this data was last mutated. /// /// # Warning @@ -126,6 +138,12 @@ pub trait DetectChangesMut: DetectChanges { /// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChangesMut::bypass_change_detection) instead. fn set_last_changed(&mut self, last_changed: Tick); + /// Manually sets the added tick recording the time when this data was last added. + /// + /// # Warning + /// The caveats of [`set_last_changed`](DetectChangesMut::set_last_changed) apply. This modifies both the added and changed ticks together. + fn set_last_added(&mut self, last_added: Tick); + /// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick. /// /// # Warning @@ -340,6 +358,11 @@ macro_rules! change_detection_impl { *self.ticks.changed } + #[inline] + fn added(&self) -> Tick { + *self.ticks.added + } + #[inline] fn changed_by(&self) -> MaybeLocation { self.changed_by.copied() @@ -376,6 +399,14 @@ macro_rules! change_detection_mut_impl { self.changed_by.assign(MaybeLocation::caller()); } + #[inline] + #[track_caller] + fn set_added(&mut self) { + *self.ticks.changed = self.ticks.this_run; + *self.ticks.added = self.ticks.this_run; + self.changed_by.assign(MaybeLocation::caller()); + } + #[inline] #[track_caller] fn set_last_changed(&mut self, last_changed: Tick) { @@ -383,6 +414,14 @@ macro_rules! change_detection_mut_impl { self.changed_by.assign(MaybeLocation::caller()); } + #[inline] + #[track_caller] + fn set_last_added(&mut self, last_added: Tick) { + *self.ticks.added = last_added; + *self.ticks.changed = last_added; + self.changed_by.assign(MaybeLocation::caller()); + } + #[inline] fn bypass_change_detection(&mut self) -> &mut Self::Inner { self.value @@ -1139,6 +1178,11 @@ impl<'w> DetectChanges for MutUntyped<'w> { fn changed_by(&self) -> MaybeLocation { self.changed_by.copied() } + + #[inline] + fn added(&self) -> Tick { + *self.ticks.added + } } impl<'w> DetectChangesMut for MutUntyped<'w> { @@ -1151,6 +1195,14 @@ impl<'w> DetectChangesMut for MutUntyped<'w> { self.changed_by.assign(MaybeLocation::caller()); } + #[inline] + #[track_caller] + fn set_added(&mut self) { + *self.ticks.changed = self.ticks.this_run; + *self.ticks.added = self.ticks.this_run; + self.changed_by.assign(MaybeLocation::caller()); + } + #[inline] #[track_caller] fn set_last_changed(&mut self, last_changed: Tick) { @@ -1158,6 +1210,14 @@ impl<'w> DetectChangesMut for MutUntyped<'w> { self.changed_by.assign(MaybeLocation::caller()); } + #[inline] + #[track_caller] + fn set_last_added(&mut self, last_added: Tick) { + *self.ticks.added = last_added; + *self.ticks.changed = last_added; + self.changed_by.assign(MaybeLocation::caller()); + } + #[inline] #[track_caller] fn bypass_change_detection(&mut self) -> &mut Self::Inner { @@ -1457,7 +1517,7 @@ impl MaybeLocation { /// within a non-tracked function body. #[inline] #[track_caller] - pub fn caller() -> Self { + pub const fn caller() -> Self { // Note that this cannot use `new_with`, since `FnOnce` invocations cannot be annotated with `#[track_caller]`. MaybeLocation { #[cfg(feature = "track_location")] @@ -1787,8 +1847,7 @@ mod tests { let mut new = value.map_unchanged(|ptr| { // SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`. - let value = unsafe { reflect_from_ptr.as_reflect_mut(ptr) }; - value + unsafe { reflect_from_ptr.as_reflect_mut(ptr) } }); assert!(!new.is_changed()); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 9a2bdff16e..80e60a8860 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -4,19 +4,19 @@ use crate::{ archetype::ArchetypeFlags, bundle::BundleInfo, change_detection::{MaybeLocation, MAX_CHANGE_AGE}, - entity::{ComponentCloneCtx, Entity, SourceComponent}, + entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent}, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, resource::Resource, storage::{SparseSetIndex, SparseSets, Table, TableRow}, - system::{Commands, Local, SystemParam}, + system::{Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, }; use alloc::boxed::Box; use alloc::{borrow::Cow, format, vec::Vec}; pub use bevy_ecs_macros::Component; -use bevy_platform_support::sync::Arc; -use bevy_platform_support::{ +use bevy_platform::sync::Arc; +use bevy_platform::{ collections::{HashMap, HashSet}, sync::PoisonError, }; @@ -160,16 +160,73 @@ use thiserror::Error; /// assert_eq!(&C(0), world.entity(id).get::().unwrap()); /// ``` /// -/// You can also define a custom constructor function or closure: +/// You can define inline component values that take the following forms: +/// ``` +/// # use bevy_ecs::prelude::*; +/// #[derive(Component)] +/// #[require( +/// B(1), // tuple structs +/// C { // named-field structs +/// x: 1, +/// ..Default::default() +/// }, +/// D::One, // enum variants +/// E::ONE, // associated consts +/// F::new(1) // constructors +/// )] +/// struct A; +/// +/// #[derive(Component, PartialEq, Eq, Debug)] +/// struct B(u8); +/// +/// #[derive(Component, PartialEq, Eq, Debug, Default)] +/// struct C { +/// x: u8, +/// y: u8, +/// } +/// +/// #[derive(Component, PartialEq, Eq, Debug)] +/// enum D { +/// Zero, +/// One, +/// } +/// +/// #[derive(Component, PartialEq, Eq, Debug)] +/// struct E(u8); +/// +/// impl E { +/// pub const ONE: Self = Self(1); +/// } +/// +/// #[derive(Component, PartialEq, Eq, Debug)] +/// struct F(u8); +/// +/// impl F { +/// fn new(value: u8) -> Self { +/// Self(value) +/// } +/// } +/// +/// # let mut world = World::default(); +/// let id = world.spawn(A).id(); +/// assert_eq!(&B(1), world.entity(id).get::().unwrap()); +/// assert_eq!(&C { x: 1, y: 0 }, world.entity(id).get::().unwrap()); +/// assert_eq!(&D::One, world.entity(id).get::().unwrap()); +/// assert_eq!(&E(1), world.entity(id).get::().unwrap()); +/// assert_eq!(&F(1), world.entity(id).get::().unwrap()); +/// ```` +/// +/// +/// You can also define arbitrary expressions by using `=` /// /// ``` /// # use bevy_ecs::prelude::*; /// #[derive(Component)] -/// #[require(C(init_c))] +/// #[require(C = init_c())] /// struct A; /// /// #[derive(Component, PartialEq, Eq, Debug)] -/// #[require(C(|| C(20)))] +/// #[require(C = C(20))] /// struct B; /// /// #[derive(Component, PartialEq, Eq, Debug)] @@ -180,6 +237,10 @@ use thiserror::Error; /// } /// /// # let mut world = World::default(); +/// // This will implicitly also insert C with the init_c() constructor +/// let id = world.spawn(A).id(); +/// assert_eq!(&C(10), world.entity(id).get::().unwrap()); +/// /// // This will implicitly also insert C with the `|| C(20)` constructor closure /// let id = world.spawn(B).id(); /// assert_eq!(&C(20), world.entity(id).get::().unwrap()); @@ -220,13 +281,13 @@ use thiserror::Error; /// struct X(usize); /// /// #[derive(Component, Default)] -/// #[require(X(|| X(1)))] +/// #[require(X(1))] /// struct Y; /// /// #[derive(Component)] /// #[require( /// Y, -/// X(|| X(2)), +/// X(2), /// )] /// struct Z; /// @@ -357,6 +418,21 @@ use thiserror::Error; /// println!("{message}"); /// } /// } +/// +/// ``` +/// # Setting the clone behavior +/// +/// You can specify how the [`Component`] is cloned when deriving it. +/// +/// Your options are the functions and variants of [`ComponentCloneBehavior`] +/// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority. +/// ``` +/// # use bevy_ecs::prelude::*; +/// +/// #[derive(Component)] +/// #[component(clone_behavior = Ignore)] +/// struct MyComponent; +/// /// ``` /// /// # Implementing the trait for foreign types @@ -405,7 +481,7 @@ use thiserror::Error; /// ``` /// # use std::cell::RefCell; /// # use bevy_ecs::component::Component; -/// use bevy_utils::synccell::SyncCell; +/// use bevy_platform::cell::SyncCell; /// /// // This will compile. /// #[derive(Component)] @@ -414,7 +490,7 @@ use thiserror::Error; /// } /// ``` /// -/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`SyncCell`]: bevy_platform::cell::SyncCell /// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html #[diagnostic::on_unimplemented( message = "`{Self}` is not a `Component`", @@ -433,15 +509,6 @@ pub trait Component: Send + Sync + 'static { /// * For a component to be immutable, this type must be [`Immutable`]. type Mutability: ComponentMutability; - /// Called when registering this component, allowing mutable access to its [`ComponentHooks`]. - #[deprecated( - since = "0.16.0", - note = "Use the individual hook methods instead (e.g., `Component::on_add`, etc.)" - )] - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.update_from_component::(); - } - /// Gets the `on_add` [`ComponentHook`] for this [`Component`] if one is defined. fn on_add() -> Option { None @@ -485,14 +552,32 @@ pub trait Component: Send + Sync + 'static { ComponentCloneBehavior::Default } - /// Visits entities stored on the component. + /// Maps the entities on this component using the given [`EntityMapper`]. This is used to remap entities in contexts like scenes and entity cloning. + /// When deriving [`Component`], this is populated by annotating fields containing entities with `#[entities]` + /// + /// ``` + /// # use bevy_ecs::{component::Component, entity::Entity}; + /// #[derive(Component)] + /// struct Inventory { + /// #[entities] + /// items: Vec + /// } + /// ``` + /// + /// Fields with `#[entities]` must implement [`MapEntities`](crate::entity::MapEntities). + /// + /// Bevy provides various implementations of [`MapEntities`](crate::entity::MapEntities), so that arbitrary combinations like these are supported with `#[entities]`: + /// + /// ```rust + /// # use bevy_ecs::{component::Component, entity::Entity}; + /// #[derive(Component)] + /// struct Inventory { + /// #[entities] + /// items: Vec> + /// } + /// ``` #[inline] - fn visit_entities(_this: &Self, _f: impl FnMut(Entity)) {} - - /// Returns pointers to every entity stored on the component. This will be used to remap entity references when this entity - /// is cloned. - #[inline] - fn visit_entities_mut(_this: &mut Self, _f: impl FnMut(&mut Entity)) {} + fn map_entities(_this: &mut Self, _mapper: &mut E) {} } mod private { @@ -600,14 +685,14 @@ pub struct HookContext { /// This information is stored in the [`ComponentInfo`] of the associated component. /// /// There is two ways of configuring hooks for a component: -/// 1. Defining the [`Component::register_component_hooks`] method (see [`Component`]) +/// 1. Defining the relevant hooks on the [`Component`] implementation /// 2. Using the [`World::register_component_hooks`] method /// /// # Example 2 /// /// ``` /// use bevy_ecs::prelude::*; -/// use bevy_platform_support::collections::HashSet; +/// use bevy_platform::collections::HashSet; /// /// #[derive(Component)] /// struct MyTrackedComponent; @@ -1126,7 +1211,7 @@ impl ComponentDescriptor { } /// Function type that can be used to clone an entity. -pub type ComponentCloneFn = fn(&mut Commands, &SourceComponent, &mut ComponentCloneCtx); +pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx); /// The clone behavior to use when cloning a [`Component`]. #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -1175,8 +1260,9 @@ impl ComponentCloneBehavior { /// A queued component registration. struct QueuedRegistration { - registrator: Box, + registrator: Box, id: ComponentId, + descriptor: ComponentDescriptor, } impl QueuedRegistration { @@ -1187,17 +1273,19 @@ impl QueuedRegistration { /// [`ComponentId`] must be unique. unsafe fn new( id: ComponentId, - func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, + descriptor: ComponentDescriptor, + func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static, ) -> Self { Self { registrator: Box::new(func), id, + descriptor, } } /// Performs the registration, returning the now valid [`ComponentId`]. fn register(self, registrator: &mut ComponentsRegistrator) -> ComponentId { - (self.registrator)(registrator, self.id); + (self.registrator)(registrator, self.id, self.descriptor); self.id } } @@ -1234,7 +1322,7 @@ impl Debug for QueuedComponents { /// Generates [`ComponentId`]s. #[derive(Debug, Default)] pub struct ComponentIds { - next: bevy_platform_support::sync::atomic::AtomicUsize, + next: bevy_platform::sync::atomic::AtomicUsize, } impl ComponentIds { @@ -1242,7 +1330,7 @@ impl ComponentIds { pub fn peek(&self) -> ComponentId { ComponentId( self.next - .load(bevy_platform_support::sync::atomic::Ordering::Relaxed), + .load(bevy_platform::sync::atomic::Ordering::Relaxed), ) } @@ -1250,7 +1338,7 @@ impl ComponentIds { pub fn next(&self) -> ComponentId { ComponentId( self.next - .fetch_add(1, bevy_platform_support::sync::atomic::Ordering::Relaxed), + .fetch_add(1, bevy_platform::sync::atomic::Ordering::Relaxed), ) } @@ -1294,6 +1382,7 @@ impl ComponentIds { /// /// As a rule of thumb, if you have mutable access to [`ComponentsRegistrator`], prefer to use that instead. /// Use this only if you need to know the id of a component but do not need to modify the contents of the world based on that id. +#[derive(Clone, Copy)] pub struct ComponentsQueuedRegistrator<'w> { components: &'w Components, ids: &'w ComponentIds, @@ -1326,7 +1415,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe fn force_register_arbitrary_component( &self, type_id: TypeId, - func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, + descriptor: ComponentDescriptor, + func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static, ) -> ComponentId { let id = self.ids.next(); self.components @@ -1337,7 +1427,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .insert( type_id, // SAFETY: The id was just generated. - unsafe { QueuedRegistration::new(id, func) }, + unsafe { QueuedRegistration::new(id, descriptor, func) }, ); id } @@ -1350,7 +1440,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe fn force_register_arbitrary_resource( &self, type_id: TypeId, - func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, + descriptor: ComponentDescriptor, + func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static, ) -> ComponentId { let id = self.ids.next(); self.components @@ -1361,7 +1452,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .insert( type_id, // SAFETY: The id was just generated. - unsafe { QueuedRegistration::new(id, func) }, + unsafe { QueuedRegistration::new(id, descriptor, func) }, ); id } @@ -1369,7 +1460,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// Queues this function to run as a dynamic registrator. fn force_register_arbitrary_dynamic( &self, - func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, + descriptor: ComponentDescriptor, + func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static, ) -> ComponentId { let id = self.ids.next(); self.components @@ -1379,7 +1471,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .dynamic_registrations .push( // SAFETY: The id was just generated. - unsafe { QueuedRegistration::new(id, func) }, + unsafe { QueuedRegistration::new(id, descriptor, func) }, ); id } @@ -1388,6 +1480,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// This will reserve an id and queue the registration. /// These registrations will be carried out at the next opportunity. /// + /// If this has already been registered or queued, this returns the previous [`ComponentId`]. + /// /// # Note /// /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. @@ -1397,13 +1491,17 @@ impl<'w> ComponentsQueuedRegistrator<'w> { self.component_id::().unwrap_or_else(|| { // SAFETY: We just checked that this type was not in the queue. unsafe { - self.force_register_arbitrary_component(TypeId::of::(), |registrator, id| { - // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. - #[expect(unused_unsafe, reason = "More precise to specify.")] - unsafe { - registrator.register_component_unchecked::(&mut Vec::new(), id); - } - }) + self.force_register_arbitrary_component( + TypeId::of::(), + ComponentDescriptor::new::(), + |registrator, id, _descriptor| { + // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. + #[expect(unused_unsafe, reason = "More precise to specify.")] + unsafe { + registrator.register_component_unchecked::(&mut Vec::new(), id); + } + }, + ) } }) } @@ -1421,7 +1519,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { &self, descriptor: ComponentDescriptor, ) -> ComponentId { - self.force_register_arbitrary_dynamic(|registrator, id| { + self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| { // SAFETY: Id uniqueness handled by caller. unsafe { registrator.register_component_inner(id, descriptor); @@ -1433,6 +1531,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// This will reserve an id and queue the registration. /// These registrations will be carried out at the next opportunity. /// + /// If this has already been registered or queued, this returns the previous [`ComponentId`]. + /// /// # Note /// /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. @@ -1443,16 +1543,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> { self.get_resource_id(type_id).unwrap_or_else(|| { // SAFETY: We just checked that this type was not in the queue. unsafe { - self.force_register_arbitrary_resource(type_id, move |registrator, id| { - // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. - // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. - #[expect(unused_unsafe, reason = "More precise to specify.")] - unsafe { - registrator.register_resource_unchecked_with(type_id, id, || { - ComponentDescriptor::new_resource::() - }); - } - }) + self.force_register_arbitrary_resource( + type_id, + ComponentDescriptor::new_resource::(), + move |registrator, id, descriptor| { + // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. + // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. + #[expect(unused_unsafe, reason = "More precise to specify.")] + unsafe { + registrator.register_resource_unchecked(type_id, id, descriptor); + } + }, + ) } }) } @@ -1461,6 +1563,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// This will reserve an id and queue the registration. /// These registrations will be carried out at the next opportunity. /// + /// If this has already been registered or queued, this returns the previous [`ComponentId`]. + /// /// # Note /// /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. @@ -1471,16 +1575,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> { self.get_resource_id(type_id).unwrap_or_else(|| { // SAFETY: We just checked that this type was not in the queue. unsafe { - self.force_register_arbitrary_resource(type_id, move |registrator, id| { - // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. - // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. - #[expect(unused_unsafe, reason = "More precise to specify.")] - unsafe { - registrator.register_resource_unchecked_with(type_id, id, || { - ComponentDescriptor::new_non_send::(StorageType::default()) - }); - } - }) + self.force_register_arbitrary_resource( + type_id, + ComponentDescriptor::new_non_send::(StorageType::default()), + move |registrator, id, descriptor| { + // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. + // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. + #[expect(unused_unsafe, reason = "More precise to specify.")] + unsafe { + registrator.register_resource_unchecked(type_id, id, descriptor); + } + }, + ) } }) } @@ -1498,7 +1604,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { &self, descriptor: ComponentDescriptor, ) -> ComponentId { - self.force_register_arbitrary_dynamic(|registrator, id| { + self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| { // SAFETY: Id uniqueness handled by caller. unsafe { registrator.register_component_inner(id, descriptor); @@ -1695,12 +1801,7 @@ impl<'w> ComponentsRegistrator<'w> { .debug_checked_unwrap() }; - #[expect( - deprecated, - reason = "need to use this method until it is removed to ensure user defined components register hooks correctly" - )] - // TODO: Replace with `info.hooks.update_from_component::();` once `Component::register_component_hooks` is removed - T::register_component_hooks(&mut info.hooks); + info.hooks.update_from_component::(); info.required_components = required_components; } @@ -1802,7 +1903,7 @@ impl<'w> ComponentsRegistrator<'w> { } } - /// Same as [`Components::register_resource_unchecked_with`] but handles safety. + /// Same as [`Components::register_resource_unchecked`] but handles safety. /// /// # Safety /// @@ -1833,7 +1934,7 @@ impl<'w> ComponentsRegistrator<'w> { let id = self.ids.next_mut(); // SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`] unsafe { - self.register_resource_unchecked_with(type_id, id, descriptor); + self.register_resource_unchecked(type_id, id, descriptor()); } id } @@ -1870,7 +1971,7 @@ pub struct Components { indices: TypeIdMap, resource_indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occor. - queued: bevy_platform_support::sync::RwLock, + queued: bevy_platform::sync::RwLock, } impl Components { @@ -1959,13 +2060,53 @@ impl Components { self.components.get(id.0).and_then(|info| info.as_ref()) } - /// Returns the name associated with the given component, if it is registered. - /// This will return `None` if the id is not regiserted or is queued. + /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present. + /// This will return `None` only if the id is neither registered nor queued to be registered. + /// + /// Currently, the [`Cow`] will be [`Cow::Owned`] if and only if the component is queued. It will be [`Cow::Borrowed`] otherwise. /// /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. #[inline] - pub fn get_name(&self, id: ComponentId) -> Option<&str> { - self.get_info(id).map(ComponentInfo::name) + pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option> { + self.components + .get(id.0) + .and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor))) + .or_else(|| { + let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); + // first check components, then resources, then dynamic + queued + .components + .values() + .chain(queued.resources.values()) + .chain(queued.dynamic_registrations.iter()) + .find(|queued| queued.id == id) + .map(|queued| Cow::Owned(queued.descriptor.clone())) + }) + } + + /// Gets the name of the component with this [`ComponentId`] if it is present. + /// This will return `None` only if the id is neither registered nor queued to be registered. + /// + /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. + #[inline] + pub fn get_name<'a>(&'a self, id: ComponentId) -> Option> { + self.components + .get(id.0) + .and_then(|info| { + info.as_ref() + .map(|info| Cow::Borrowed(info.descriptor.name())) + }) + .or_else(|| { + let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); + // first check components, then resources, then dynamic + queued + .components + .values() + .chain(queued.resources.values()) + .chain(queued.dynamic_registrations.iter()) + .find(|queued| queued.id == id) + .map(|queued| queued.descriptor.name.clone()) + }) } /// Gets the metadata associated with the given component. @@ -2388,15 +2529,15 @@ impl Components { /// The [`ComponentId`] must be unique. /// The [`TypeId`] and [`ComponentId`] must not be registered or queued. #[inline] - unsafe fn register_resource_unchecked_with( + unsafe fn register_resource_unchecked( &mut self, type_id: TypeId, component_id: ComponentId, - func: impl FnOnce() -> ComponentDescriptor, + descriptor: ComponentDescriptor, ) { // SAFETY: ensured by caller unsafe { - self.register_component_inner(component_id, func()); + self.register_component_inner(component_id, descriptor); } let prev = self.resource_indices.insert(type_id, component_id); debug_assert!(prev.is_none()); @@ -2711,7 +2852,7 @@ impl RequiredComponents { ) { let entry = self.0.entry(component_id); match entry { - bevy_platform_support::collections::hash_map::Entry::Occupied(mut occupied) => { + bevy_platform::collections::hash_map::Entry::Occupied(mut occupied) => { let current = occupied.get_mut(); if current.inheritance_depth > inheritance_depth { *current = RequiredComponent { @@ -2720,7 +2861,7 @@ impl RequiredComponents { } } } - bevy_platform_support::collections::hash_map::Entry::Vacant(vacant) => { + bevy_platform::collections::hash_map::Entry::Vacant(vacant) => { vacant.insert(RequiredComponent { constructor: constructor(), inheritance_depth, @@ -2872,13 +3013,13 @@ pub fn enforce_no_required_components_recursion( "Recursive required components detected: {}\nhelp: {}", recursion_check_stack .iter() - .map(|id| format!("{}", ShortName(components.get_name(*id).unwrap()))) + .map(|id| format!("{}", ShortName(&components.get_name(*id).unwrap()))) .collect::>() .join(" → "), if direct_recursion { format!( "Remove require({}).", - ShortName(components.get_name(requiree).unwrap()) + ShortName(&components.get_name(requiree).unwrap()) ) } else { "If this is intentional, consider merging the components.".into() @@ -2893,7 +3034,6 @@ pub fn enforce_no_required_components_recursion( /// It will panic if set as handler for any other component. /// pub fn component_clone_via_clone( - _commands: &mut Commands, source: &SourceComponent, ctx: &mut ComponentCloneCtx, ) { @@ -2920,11 +3060,7 @@ pub fn component_clone_via_clone( /// /// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone #[cfg(feature = "bevy_reflect")] -pub fn component_clone_via_reflect( - commands: &mut Commands, - source: &SourceComponent, - ctx: &mut ComponentCloneCtx, -) { +pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut ComponentCloneCtx) { let Some(app_registry) = ctx.type_registry().cloned() else { return; }; @@ -2941,9 +3077,7 @@ pub fn component_clone_via_reflect( if let Some(reflect_component) = registry.get_type_data::(type_id) { - reflect_component.visit_entities_mut(&mut *component, &mut |entity| { - *entity = ctx.entity_mapper().get_mapped(*entity); - }); + reflect_component.map_entities(&mut *component, ctx.entity_mapper()); } drop(registry); @@ -2961,9 +3095,7 @@ pub fn component_clone_via_reflect( if let Some(reflect_component) = registry.get_type_data::(type_id) { - reflect_component.visit_entities_mut(&mut *component, &mut |entity| { - *entity = ctx.entity_mapper().get_mapped(*entity); - }); + reflect_component.map_entities(&mut *component, ctx.entity_mapper()); } drop(registry); @@ -2986,23 +3118,12 @@ pub fn component_clone_via_reflect( registry.get_type_data::(type_id) { let reflect_from_world = reflect_from_world.clone(); - let mut mapped_entities = Vec::new(); - if let Some(reflect_component) = - registry.get_type_data::(type_id) - { - reflect_component.visit_entities(source_component_reflect, &mut |entity| { - mapped_entities.push(entity); - }); - } let source_component_cloned = source_component_reflect.to_dynamic(); let component_layout = component_info.layout(); let target = ctx.target(); let component_id = ctx.component_id(); - for entity in mapped_entities.iter_mut() { - *entity = ctx.entity_mapper().get_mapped(*entity); - } drop(registry); - commands.queue(move |world: &mut World| { + ctx.queue_deferred(move |world: &mut World, mapper: &mut dyn EntityMapper| { let mut component = reflect_from_world.from_world(world); assert_eq!(type_id, (*component).type_id()); component.apply(source_component_cloned.as_partial_reflect()); @@ -3010,11 +3131,7 @@ pub fn component_clone_via_reflect( .read() .get_type_data::(type_id) { - let mut i = 0; - reflect_component.visit_entities_mut(&mut *component, &mut |entity| { - *entity = mapped_entities[i]; - i += 1; - }); + reflect_component.map_entities(&mut *component, mapper); } // SAFETY: // - component_id is from the same world as target entity @@ -3038,12 +3155,7 @@ pub fn component_clone_via_reflect( /// Noop implementation of component clone handler function. /// /// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. -pub fn component_clone_ignore( - _commands: &mut Commands, - _source: &SourceComponent, - _ctx: &mut ComponentCloneCtx, -) { -} +pub fn component_clone_ignore(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {} /// Wrapper for components clone specialization using autoderef. #[doc(hidden)] diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 25e1534532..5328eb1d3a 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1,22 +1,15 @@ -use alloc::{borrow::ToOwned, collections::VecDeque, vec::Vec}; -use bevy_platform_support::collections::{HashMap, HashSet}; +use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, vec::Vec}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_ptr::{Ptr, PtrMut}; use bumpalo::Bump; use core::any::TypeId; -#[cfg(feature = "bevy_reflect")] -use alloc::boxed::Box; - -use crate::component::{ComponentCloneBehavior, ComponentCloneFn}; -use crate::entity::hash_map::EntityHashMap; -use crate::entity::{Entities, EntityMapper}; -use crate::relationship::RelationshipHookMode; -use crate::system::Commands; use crate::{ bundle::Bundle, - component::{Component, ComponentId, ComponentInfo}, - entity::Entity, + component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo}, + entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper}, query::DebugCheckedUnwrap, + relationship::RelationshipHookMode, world::World, }; @@ -91,7 +84,7 @@ pub struct ComponentCloneCtx<'a, 'b> { #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, #[cfg(not(feature = "bevy_reflect"))] - #[expect(dead_code)] + #[expect(dead_code, reason = "type_registry is only used with bevy_reflect")] type_registry: Option<&'a ()>, } @@ -176,9 +169,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// - Component being written is not registered in the world. /// - `ComponentId` of component being written does not match expected `ComponentId`. pub fn write_target_component(&mut self, mut component: C) { - C::visit_entities_mut(&mut component, |entity| { - *entity = self.mapper.get_mapped(*entity); - }); + C::map_entities(&mut component, &mut self.mapper); let short_name = disqualified::ShortName::of::(); if self.target_component_written { panic!("Trying to write component '{short_name}' multiple times") @@ -280,6 +271,17 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { self.mapper.set_mapped(entity, target); self.entity_cloner.clone_queue.push_back(entity); } + + /// Queues a deferred clone operation, which will run with exclusive [`World`] access immediately after calling the clone handler for each component on an entity. + /// This exists, despite its similarity to [`Commands`](crate::system::Commands), to provide access to the entity mapper in the current context. + pub fn queue_deferred( + &mut self, + deferred: impl FnOnce(&mut World, &mut dyn EntityMapper) + 'static, + ) { + self.entity_cloner + .deferred_commands + .push_back(Box::new(deferred)); + } } /// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which @@ -322,16 +324,10 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable}; -/// #[derive(Clone)] +/// #[derive(Clone, Component)] +/// #[component(clone_behavior = clone::())] /// struct SomeComponent; /// -/// impl Component for SomeComponent { -/// const STORAGE_TYPE: StorageType = StorageType::Table; -/// type Mutability = Mutable; -/// fn clone_behavior() -> ComponentCloneBehavior { -/// ComponentCloneBehavior::clone::() -/// } -/// } /// ``` /// /// # Clone Behaviors @@ -341,7 +337,6 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// 2. component-defined handler using [`Component::clone_behavior`] /// 3. default handler override using [`EntityClonerBuilder::with_default_clone_fn`]. /// 4. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not. -#[derive(Debug)] pub struct EntityCloner { filter_allows_components: bool, filter: HashSet, @@ -350,18 +345,20 @@ pub struct EntityCloner { linked_cloning: bool, default_clone_fn: ComponentCloneFn, clone_queue: VecDeque, + deferred_commands: VecDeque>, } impl Default for EntityCloner { fn default() -> Self { Self { filter_allows_components: false, - filter: Default::default(), - clone_behavior_overrides: Default::default(), move_components: false, linked_cloning: false, default_clone_fn: ComponentCloneBehavior::global_default_fn(), + filter: Default::default(), + clone_behavior_overrides: Default::default(), clone_queue: Default::default(), + deferred_commands: Default::default(), } } } @@ -476,10 +473,6 @@ impl EntityCloner { let archetype = source_entity.archetype(); bundle_scratch = BundleScratch::with_capacity(archetype.component_count()); - // SAFETY: no other references to command queue exist - let mut commands = unsafe { - Commands::new_raw_from_entities(world.get_raw_command_queue(), world.entities()) - }; for component in archetype.components() { if !self.is_cloning_allowed(&component) { @@ -527,12 +520,16 @@ impl EntityCloner { ) }; - (handler)(&mut commands, &source_component, &mut ctx); + (handler)(&source_component, &mut ctx); } } world.flush(); + for deferred in self.deferred_commands.drain(..) { + (deferred)(world, mapper); + } + if !world.entities.contains(target) { panic!("Target entity does not exist"); } @@ -609,7 +606,6 @@ impl EntityCloner { } /// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information. -#[derive(Debug)] pub struct EntityClonerBuilder<'w> { world: &'w mut World, entity_cloner: EntityCloner, @@ -842,10 +838,9 @@ mod tests { use super::ComponentCloneCtx; use crate::{ component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, - entity::{hash_map::EntityHashMap, Entity, EntityCloner, SourceComponent}, + entity::{Entity, EntityCloner, EntityHashMap, SourceComponent}, prelude::{ChildOf, Children, Resource}, reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, - system::Commands, world::{FromWorld, World}, }; use alloc::vec::Vec; @@ -861,7 +856,6 @@ mod tests { component::{Component, ComponentCloneBehavior}, entity::{EntityCloner, SourceComponent}, reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, - system::Commands, }; use alloc::vec; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; @@ -991,11 +985,7 @@ mod tests { #[derive(Component, Reflect)] struct B; - fn test_handler( - _commands: &mut Commands, - source: &SourceComponent, - ctx: &mut ComponentCloneCtx, - ) { + fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) { let registry = ctx.type_registry().unwrap(); assert!(source.read_reflect(®istry.read()).is_none()); } @@ -1233,7 +1223,7 @@ mod tests { struct A; #[derive(Component, Clone, PartialEq, Debug, Default)] - #[require(C(|| C(5)))] + #[require(C(5))] struct B; #[derive(Component, Clone, PartialEq, Debug)] @@ -1261,7 +1251,7 @@ mod tests { struct A; #[derive(Component, Clone, PartialEq, Debug, Default)] - #[require(C(|| C(5)))] + #[require(C(5))] struct B; #[derive(Component, Clone, PartialEq, Debug)] @@ -1287,11 +1277,7 @@ mod tests { #[test] fn clone_entity_with_dynamic_components() { const COMPONENT_SIZE: usize = 10; - fn test_handler( - _commands: &mut Commands, - source: &SourceComponent, - ctx: &mut ComponentCloneCtx, - ) { + fn test_handler(source: &SourceComponent, ctx: &mut ComponentCloneCtx) { // SAFETY: the passed in ptr corresponds to copy-able data that matches the type of the source / target component unsafe { ctx.write_target_component_ptr(source.ptr()); @@ -1345,9 +1331,9 @@ mod tests { fn recursive_clone() { let mut world = World::new(); let root = world.spawn_empty().id(); - let child1 = world.spawn(ChildOf { parent: root }).id(); - let grandchild = world.spawn(ChildOf { parent: child1 }).id(); - let child2 = world.spawn(ChildOf { parent: root }).id(); + let child1 = world.spawn(ChildOf(root)).id(); + let grandchild = world.spawn(ChildOf(child1)).id(); + let child2 = world.spawn(ChildOf(root)).id(); let clone_root = world.spawn_empty().id(); EntityCloner::build(&mut world) diff --git a/crates/bevy_ecs/src/entity/entity_set.rs b/crates/bevy_ecs/src/entity/entity_set.rs index d93debcbc0..e4860685fe 100644 --- a/crates/bevy_ecs/src/entity/entity_set.rs +++ b/crates/bevy_ecs/src/entity/entity_set.rs @@ -3,7 +3,7 @@ use alloc::{ collections::{btree_map, btree_set}, rc::Rc, }; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use core::{ array, @@ -13,40 +13,61 @@ use core::{ option, result, }; -use super::{unique_slice::UniqueEntitySlice, Entity}; +use super::{Entity, UniqueEntityEquivalentSlice}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; -/// A trait for entity borrows. +/// A trait for types that contain an [`Entity`]. /// -/// This trait can be thought of as `Borrow`, but yielding `Entity` directly. -pub trait EntityBorrow { - /// Returns the borrowed entity. +/// This trait behaves similarly to `Borrow`, but yielding `Entity` directly. +/// +/// It should only be implemented when: +/// - Retrieving the [`Entity`] is a simple operation. +/// - The [`Entity`] contained by the type is unambiguous. +pub trait ContainsEntity { + /// Returns the contained entity. fn entity(&self) -> Entity; } -/// A trait for [`Entity`] borrows with trustworthy comparison behavior. +/// A trait for types that represent an [`Entity`]. /// -/// Comparison trait behavior between a [`TrustedEntityBorrow`] type and its underlying entity will match. +/// Comparison trait behavior between an [`EntityEquivalent`] type and its underlying entity will match. /// This property includes [`PartialEq`], [`Eq`], [`PartialOrd`], [`Ord`] and [`Hash`], /// and remains even after [`Clone`] and/or [`Borrow`] calls. /// /// # Safety -/// Any [`PartialEq`], [`Eq`], [`PartialOrd`], [`Ord`], and [`Hash`] impls must be -/// equivalent for `Self` and its underlying entity: -/// `x.entity() == y.entity()` should give the same result as `x == y`. -/// The above equivalence must also hold through and between calls to any [`Clone`] -/// and [`Borrow`]/[`BorrowMut`] impls in place of [`entity()`]. +/// Any [`PartialEq`], [`Eq`], [`PartialOrd`], and [`Ord`] impls must evaluate the same for `Self` and +/// its underlying entity. +/// `x.entity() == y.entity()` must be equivalent to `x == y`. +/// +/// The above equivalence must also hold through and between calls to any [`Clone`] and +/// [`Borrow`]/[`BorrowMut`] impls in place of [`entity()`]. /// /// The result of [`entity()`] must be unaffected by any interior mutability. /// +/// The aforementioned properties imply determinism in both [`entity()`] calls +/// and comparison trait behavior. +/// +/// All [`Hash`] impls except that for [`Entity`] must delegate to the [`Hash`] impl of +/// another [`EntityEquivalent`] type. All conversions to the delegatee within the [`Hash`] impl must +/// follow [`entity()`] equivalence. +/// +/// It should be noted that [`Hash`] is *not* a comparison trait, and with [`Hash::hash`] being forcibly +/// generic over all [`Hasher`]s, **cannot** guarantee determinism or uniqueness of any final hash values +/// on its own. +/// To obtain hash values forming the same total order as [`Entity`], any [`Hasher`] used must be +/// deterministic and concerning [`Entity`], collisionless. +/// Standard library hash collections handle collisions with an [`Eq`] fallback, but do not account for +/// determinism when [`BuildHasher`] is unspecified,. +/// /// [`Hash`]: core::hash::Hash +/// [`Hasher`]: core::hash::Hasher /// [`Borrow`]: core::borrow::Borrow /// [`BorrowMut`]: core::borrow::BorrowMut -/// [`entity()`]: EntityBorrow::entity -pub unsafe trait TrustedEntityBorrow: EntityBorrow + Eq {} +/// [`entity()`]: ContainsEntity::entity +pub unsafe trait EntityEquivalent: ContainsEntity + Eq {} -impl EntityBorrow for Entity { +impl ContainsEntity for Entity { fn entity(&self) -> Entity { *self } @@ -54,9 +75,9 @@ impl EntityBorrow for Entity { // SAFETY: // The trait implementations of Entity are correct and deterministic. -unsafe impl TrustedEntityBorrow for Entity {} +unsafe impl EntityEquivalent for Entity {} -impl EntityBorrow for &T { +impl ContainsEntity for &T { fn entity(&self) -> Entity { (**self).entity() } @@ -66,9 +87,9 @@ impl EntityBorrow for &T { // `&T` delegates `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` to T. // `Clone` and `Borrow` maintain equality. // `&T` is `Freeze`. -unsafe impl TrustedEntityBorrow for &T {} +unsafe impl EntityEquivalent for &T {} -impl EntityBorrow for &mut T { +impl ContainsEntity for &mut T { fn entity(&self) -> Entity { (**self).entity() } @@ -78,9 +99,9 @@ impl EntityBorrow for &mut T { // `&mut T` delegates `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` to T. // `Borrow` and `BorrowMut` maintain equality. // `&mut T` is `Freeze`. -unsafe impl TrustedEntityBorrow for &mut T {} +unsafe impl EntityEquivalent for &mut T {} -impl EntityBorrow for Box { +impl ContainsEntity for Box { fn entity(&self) -> Entity { (**self).entity() } @@ -90,9 +111,9 @@ impl EntityBorrow for Box { // `Box` delegates `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` to T. // `Clone`, `Borrow` and `BorrowMut` maintain equality. // `Box` is `Freeze`. -unsafe impl TrustedEntityBorrow for Box {} +unsafe impl EntityEquivalent for Box {} -impl EntityBorrow for Rc { +impl ContainsEntity for Rc { fn entity(&self) -> Entity { (**self).entity() } @@ -102,9 +123,9 @@ impl EntityBorrow for Rc { // `Rc` delegates `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` to T. // `Clone`, `Borrow` and `BorrowMut` maintain equality. // `Rc` is `Freeze`. -unsafe impl TrustedEntityBorrow for Rc {} +unsafe impl EntityEquivalent for Rc {} -impl EntityBorrow for Arc { +impl ContainsEntity for Arc { fn entity(&self) -> Entity { (**self).entity() } @@ -114,7 +135,7 @@ impl EntityBorrow for Arc { // `Arc` delegates `PartialEq`, `Eq`, `PartialOrd`, `Ord`, and `Hash` to T. // `Clone`, `Borrow` and `BorrowMut` maintain equality. // `Arc` is `Freeze`. -unsafe impl TrustedEntityBorrow for Arc {} +unsafe impl EntityEquivalent for Arc {} /// A set of unique entities. /// @@ -146,7 +167,7 @@ impl> EntitySet for T {} /// /// `x != y` must hold for any 2 elements returned by the iterator. /// This is always true for iterators that cannot return more than one element. -pub unsafe trait EntitySetIterator: Iterator { +pub unsafe trait EntitySetIterator: Iterator { /// Transforms an `EntitySetIterator` into a collection. /// /// This is a specialized form of [`collect`], for collections which benefit from the uniqueness guarantee. @@ -164,89 +185,86 @@ pub unsafe trait EntitySetIterator: Iterator { // SAFETY: // A correct `BTreeMap` contains only unique keys. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeMap`. -unsafe impl EntitySetIterator for btree_map::Keys<'_, K, V> {} +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeMap`. +unsafe impl EntitySetIterator for btree_map::Keys<'_, K, V> {} // SAFETY: // A correct `BTreeMap` contains only unique keys. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeMap`. -unsafe impl EntitySetIterator for btree_map::IntoKeys {} +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeMap`. +unsafe impl EntitySetIterator for btree_map::IntoKeys {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. // The sub-range maintains uniqueness. -unsafe impl EntitySetIterator for btree_set::Range<'_, T> {} +unsafe impl EntitySetIterator for btree_set::Range<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. // The "intersection" operation maintains uniqueness. -unsafe impl EntitySetIterator for btree_set::Intersection<'_, T> {} +unsafe impl EntitySetIterator for btree_set::Intersection<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. // The "union" operation maintains uniqueness. -unsafe impl EntitySetIterator for btree_set::Union<'_, T> {} +unsafe impl EntitySetIterator for btree_set::Union<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. // The "difference" operation maintains uniqueness. -unsafe impl EntitySetIterator for btree_set::Difference<'_, T> {} +unsafe impl EntitySetIterator for btree_set::Difference<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. // The "symmetric difference" operation maintains uniqueness. -unsafe impl EntitySetIterator - for btree_set::SymmetricDifference<'_, T> -{ -} +unsafe impl EntitySetIterator for btree_set::SymmetricDifference<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. -unsafe impl EntitySetIterator for btree_set::Iter<'_, T> {} +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +unsafe impl EntitySetIterator for btree_set::Iter<'_, T> {} // SAFETY: // A correct `BTreeSet` contains only unique elements. -// TrustedEntityBorrow guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. -unsafe impl EntitySetIterator for btree_set::IntoIter {} +// EntityEquivalent guarantees a trustworthy Ord impl for T, and thus a correct `BTreeSet`. +unsafe impl EntitySetIterator for btree_set::IntoIter {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for option::Iter<'_, T> {} +unsafe impl EntitySetIterator for option::Iter<'_, T> {} // SAFETY: This iterator only returns one element. -// unsafe impl EntitySetIterator for option::IterMut<'_, T> {} +// unsafe impl EntitySetIterator for option::IterMut<'_, T> {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for option::IntoIter {} +unsafe impl EntitySetIterator for option::IntoIter {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for result::Iter<'_, T> {} +unsafe impl EntitySetIterator for result::Iter<'_, T> {} // SAFETY: This iterator only returns one element. -// unsafe impl EntitySetIterator for result::IterMut<'_, T> {} +// unsafe impl EntitySetIterator for result::IterMut<'_, T> {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for result::IntoIter {} +unsafe impl EntitySetIterator for result::IntoIter {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for array::IntoIter {} +unsafe impl EntitySetIterator for array::IntoIter {} // SAFETY: This iterator does not return any elements. -unsafe impl EntitySetIterator for array::IntoIter {} +unsafe impl EntitySetIterator for array::IntoIter {} // SAFETY: This iterator only returns one element. -unsafe impl T> EntitySetIterator for iter::OnceWith {} +unsafe impl T> EntitySetIterator for iter::OnceWith {} // SAFETY: This iterator only returns one element. -unsafe impl EntitySetIterator for iter::Once {} +unsafe impl EntitySetIterator for iter::Once {} // SAFETY: This iterator does not return any elements. -unsafe impl EntitySetIterator for iter::Empty {} +unsafe impl EntitySetIterator for iter::Empty {} // SAFETY: Taking a mutable reference of an iterator has no effect on its elements. unsafe impl EntitySetIterator for &mut I {} @@ -254,14 +272,14 @@ unsafe impl EntitySetIterator for &mut I {} // SAFETY: Boxing an iterator has no effect on its elements. unsafe impl EntitySetIterator for Box {} -// SAFETY: TrustedEntityBorrow ensures that Copy does not affect equality, via its restrictions on Clone. -unsafe impl<'a, T: 'a + TrustedEntityBorrow + Copy, I: EntitySetIterator> +// SAFETY: EntityEquivalent ensures that Copy does not affect equality, via its restrictions on Clone. +unsafe impl<'a, T: 'a + EntityEquivalent + Copy, I: EntitySetIterator> EntitySetIterator for iter::Copied { } -// SAFETY: TrustedEntityBorrow ensures that Clone does not affect equality. -unsafe impl<'a, T: 'a + TrustedEntityBorrow + Clone, I: EntitySetIterator> +// SAFETY: EntityEquivalent ensures that Clone does not affect equality. +unsafe impl<'a, T: 'a + EntityEquivalent + Clone, I: EntitySetIterator> EntitySetIterator for iter::Cloned { } @@ -277,7 +295,7 @@ unsafe impl EntitySetIterator for iter::Fuse {} // SAFETY: // Obtaining immutable references the elements of an iterator does not affect uniqueness. -// TrustedEntityBorrow ensures the lack of interior mutability. +// EntityEquivalent ensures the lack of interior mutability. unsafe impl::Item)> EntitySetIterator for iter::Inspect { @@ -316,12 +334,12 @@ unsafe impl EntitySetIterator for iter::StepBy {} /// /// See also: [`EntitySet`]. // FIXME: When subtrait item shadowing stabilizes, this should be renamed and shadow `FromIterator::from_iter` -pub trait FromEntitySetIterator: FromIterator { +pub trait FromEntitySetIterator: FromIterator { /// Creates a value from an [`EntitySetIterator`]. fn from_entity_set_iter>(set_iter: T) -> Self; } -impl FromEntitySetIterator +impl FromEntitySetIterator for HashSet { fn from_entity_set_iter>(set_iter: I) -> Self { @@ -340,7 +358,7 @@ impl FromEntitySetItera /// An iterator that yields unique entities. /// /// This wrapper can provide an [`EntitySetIterator`] implementation when an instance of `I` is known to uphold uniqueness. -pub struct UniqueEntityIter> { +pub struct UniqueEntityIter> { iter: I, } @@ -351,7 +369,7 @@ impl UniqueEntityIter { } } -impl> UniqueEntityIter { +impl> UniqueEntityIter { /// Constructs a [`UniqueEntityIter`] from an iterator unsafely. /// /// # Safety @@ -382,7 +400,7 @@ impl> UniqueEntityIter { } } -impl> Iterator for UniqueEntityIter { +impl> Iterator for UniqueEntityIter { type Item = I::Item; fn next(&mut self) -> Option { @@ -394,42 +412,40 @@ impl> Iterator for UniqueEntityIter { } } -impl> ExactSizeIterator for UniqueEntityIter {} +impl> ExactSizeIterator for UniqueEntityIter {} -impl> DoubleEndedIterator - for UniqueEntityIter -{ +impl> DoubleEndedIterator for UniqueEntityIter { fn next_back(&mut self) -> Option { self.iter.next_back() } } -impl> FusedIterator for UniqueEntityIter {} +impl> FusedIterator for UniqueEntityIter {} // SAFETY: The underlying iterator is ensured to only return unique elements by its construction. -unsafe impl> EntitySetIterator for UniqueEntityIter {} +unsafe impl> EntitySetIterator for UniqueEntityIter {} -impl + AsRef<[T]>> AsRef<[T]> for UniqueEntityIter { +impl + AsRef<[T]>> AsRef<[T]> for UniqueEntityIter { fn as_ref(&self) -> &[T] { self.iter.as_ref() } } -impl + AsRef<[T]>> - AsRef> for UniqueEntityIter +impl + AsRef<[T]>> + AsRef> for UniqueEntityIter { - fn as_ref(&self) -> &UniqueEntitySlice { + fn as_ref(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.iter.as_ref()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.iter.as_ref()) } } } -impl + AsMut<[T]>> - AsMut> for UniqueEntityIter +impl + AsMut<[T]>> + AsMut> for UniqueEntityIter { - fn as_mut(&mut self) -> &mut UniqueEntitySlice { + fn as_mut(&mut self) -> &mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.iter.as_mut()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.iter.as_mut()) } } } @@ -451,7 +467,7 @@ impl Clone for UniqueEntityIter { } } -impl + Debug> Debug for UniqueEntityIter { +impl + Debug> Debug for UniqueEntityIter { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.debug_struct("UniqueEntityIter") .field("iter", &self.iter) diff --git a/crates/bevy_ecs/src/entity/hash.rs b/crates/bevy_ecs/src/entity/hash.rs index 35e01cebcd..a538473439 100644 --- a/crates/bevy_ecs/src/entity/hash.rs +++ b/crates/bevy_ecs/src/entity/hash.rs @@ -25,7 +25,7 @@ impl BuildHasher for EntityHash { /// /// If you have an unusual case -- say all your indices are multiples of 256 /// or most of the entities are dead generations -- then you might want also to -/// try [`DefaultHasher`](bevy_platform_support::hash::DefaultHasher) for a slower hash +/// try [`DefaultHasher`](bevy_platform::hash::DefaultHasher) for a slower hash /// computation but fewer lookup conflicts. #[derive(Debug, Default)] pub struct EntityHasher { diff --git a/crates/bevy_ecs/src/entity/hash_map.rs b/crates/bevy_ecs/src/entity/hash_map.rs index 39fbfb1d87..d83ea7bae1 100644 --- a/crates/bevy_ecs/src/entity/hash_map.rs +++ b/crates/bevy_ecs/src/entity/hash_map.rs @@ -9,11 +9,11 @@ use core::{ ops::{Deref, DerefMut, Index}, }; -use bevy_platform_support::collections::hash_map::{self, HashMap}; +use bevy_platform::collections::hash_map::{self, HashMap}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; -use super::{Entity, EntityHash, EntitySetIterator, TrustedEntityBorrow}; +use super::{Entity, EntityEquivalent, EntityHash, EntitySetIterator}; /// A [`HashMap`] pre-configured to use [`EntityHash`] hashing. #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] @@ -113,7 +113,7 @@ impl FromIterator<(Entity, V)> for EntityHashMap { } } -impl Index<&Q> for EntityHashMap { +impl Index<&Q> for EntityHashMap { type Output = V; fn index(&self, key: &Q) -> &V { self.0.index(&key.entity()) diff --git a/crates/bevy_ecs/src/entity/hash_set.rs b/crates/bevy_ecs/src/entity/hash_set.rs index 91c1000c91..7fd1ae9011 100644 --- a/crates/bevy_ecs/src/entity/hash_set.rs +++ b/crates/bevy_ecs/src/entity/hash_set.rs @@ -12,7 +12,7 @@ use core::{ }, }; -use bevy_platform_support::collections::hash_set::{self, HashSet}; +use bevy_platform::collections::hash_set::{self, HashSet}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; diff --git a/crates/bevy_ecs/src/entity/index_map.rs b/crates/bevy_ecs/src/entity/index_map.rs index de174ae3c8..6f6cd1bb47 100644 --- a/crates/bevy_ecs/src/entity/index_map.rs +++ b/crates/bevy_ecs/src/entity/index_map.rs @@ -19,9 +19,9 @@ use core::{ use bevy_reflect::Reflect; use indexmap::map::{self, IndexMap, IntoValues, ValuesMut}; -use super::{Entity, EntityHash, EntitySetIterator, TrustedEntityBorrow}; +use super::{Entity, EntityEquivalent, EntityHash, EntitySetIterator}; -use bevy_platform_support::prelude::Box; +use bevy_platform::prelude::Box; /// A [`IndexMap`] pre-configured to use [`EntityHash`] hashing. #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] @@ -176,7 +176,7 @@ impl FromIterator<(Entity, V)> for EntityIndexMap { } } -impl Index<&Q> for EntityIndexMap { +impl Index<&Q> for EntityIndexMap { type Output = V; fn index(&self, key: &Q) -> &V { self.0.index(&key.entity()) @@ -246,7 +246,7 @@ impl Index for EntityIndexMap { } } -impl IndexMut<&Q> for EntityIndexMap { +impl IndexMut<&Q> for EntityIndexMap { fn index_mut(&mut self, key: &Q) -> &mut V { self.0.index_mut(&key.entity()) } diff --git a/crates/bevy_ecs/src/entity/index_set.rs b/crates/bevy_ecs/src/entity/index_set.rs index 1af46994f6..42f420a211 100644 --- a/crates/bevy_ecs/src/entity/index_set.rs +++ b/crates/bevy_ecs/src/entity/index_set.rs @@ -20,7 +20,7 @@ use indexmap::set::{self, IndexSet}; use super::{Entity, EntityHash, EntitySetIterator}; -use bevy_platform_support::prelude::Box; +use bevy_platform::prelude::Box; /// An [`IndexSet`] pre-configured to use [`EntityHash`] hashing. #[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index caa02eaeac..3dac2fa749 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -1,10 +1,20 @@ +pub use bevy_ecs_macros::MapEntities; +use indexmap::IndexSet; + use crate::{ - entity::Entity, - identifier::masks::{IdentifierMask, HIGH_MASK}, + entity::{hash_map::EntityHashMap, Entity}, world::World, }; -use super::{hash_map::EntityHashMap, VisitEntitiesMut}; +use alloc::{ + collections::{BTreeSet, VecDeque}, + vec::Vec, +}; +use bevy_platform::collections::HashSet; +use core::{hash::BuildHasher, mem}; +use smallvec::SmallVec; + +use super::EntityIndexSet; /// Operation to map all contained [`Entity`] fields in a type to new values. /// @@ -15,15 +25,11 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut}; /// (usually by using an [`EntityHashMap`] between source entities and entities in the /// current world). /// -/// This trait is similar to [`VisitEntitiesMut`]. They differ in that [`VisitEntitiesMut`] operates -/// on `&mut Entity` and allows for in-place modification, while this trait makes no assumption that -/// such in-place modification is occurring, which is impossible for types such as [`HashSet`] -/// and [`EntityHashMap`] which must be rebuilt when their contained [`Entity`]s are remapped. +/// Components use [`Component::map_entities`](crate::component::Component::map_entities) to map +/// entities in the context of scenes and entity cloning, which generally uses [`MapEntities`] internally +/// to map each field (see those docs for usage). /// -/// Implementing this trait correctly is required for properly loading components -/// with entity references from scenes. -/// -/// [`HashSet`]: bevy_platform_support::collections::HashSet +/// [`HashSet`]: bevy_platform::collections::HashSet /// /// ## Example /// @@ -49,14 +55,93 @@ pub trait MapEntities { /// /// Implementors should look up any and all [`Entity`] values stored within `self` and /// update them to the mapped values via `entity_mapper`. - fn map_entities(&mut self, entity_mapper: &mut M); + fn map_entities(&mut self, entity_mapper: &mut E); } -impl MapEntities for T { - fn map_entities(&mut self, entity_mapper: &mut M) { - self.visit_entities_mut(|entity| { - *entity = entity_mapper.get_mapped(*entity); - }); +impl MapEntities for Entity { + fn map_entities(&mut self, entity_mapper: &mut E) { + *self = entity_mapper.get_mapped(*self); + } +} + +impl MapEntities for Option { + fn map_entities(&mut self, entity_mapper: &mut E) { + if let Some(entities) = self { + entities.map_entities(entity_mapper); + } + } +} + +impl MapEntities + for HashSet +{ + fn map_entities(&mut self, entity_mapper: &mut E) { + *self = self + .drain() + .map(|mut entities| { + entities.map_entities(entity_mapper); + entities + }) + .collect(); + } +} + +impl MapEntities + for IndexSet +{ + fn map_entities(&mut self, entity_mapper: &mut E) { + *self = self + .drain(..) + .map(|mut entities| { + entities.map_entities(entity_mapper); + entities + }) + .collect(); + } +} + +impl MapEntities for EntityIndexSet { + fn map_entities(&mut self, entity_mapper: &mut E) { + *self = self + .drain(..) + .map(|e| entity_mapper.get_mapped(e)) + .collect(); + } +} + +impl MapEntities for BTreeSet { + fn map_entities(&mut self, entity_mapper: &mut E) { + *self = mem::take(self) + .into_iter() + .map(|mut entities| { + entities.map_entities(entity_mapper); + entities + }) + .collect(); + } +} + +impl MapEntities for Vec { + fn map_entities(&mut self, entity_mapper: &mut E) { + for entities in self.iter_mut() { + entities.map_entities(entity_mapper); + } + } +} + +impl MapEntities for VecDeque { + fn map_entities(&mut self, entity_mapper: &mut E) { + for entities in self.iter_mut() { + entities.map_entities(entity_mapper); + } + } +} + +impl> MapEntities for SmallVec { + fn map_entities(&mut self, entity_mapper: &mut E) { + for entities in self.iter_mut() { + entities.map_entities(entity_mapper); + } } } @@ -67,14 +152,13 @@ impl MapEntities for T { /// /// More generally, this can be used to map [`Entity`] references between any two [`Worlds`](World). /// -/// This can be used in tandem with [`Component::visit_entities`](crate::component::Component::visit_entities) -/// and [`Component::visit_entities_mut`](crate::component::Component::visit_entities_mut) to map a component's entities. +/// This is used by [`MapEntities`] implementors. /// /// ## Example /// /// ``` /// # use bevy_ecs::entity::{Entity, EntityMapper}; -/// # use bevy_ecs::entity::hash_map::EntityHashMap; +/// # use bevy_ecs::entity::EntityHashMap; /// # /// pub struct SimpleEntityMapper { /// map: EntityHashMap, @@ -86,7 +170,7 @@ impl MapEntities for T { /// fn get_mapped(&mut self, entity: Entity) -> Entity { /// self.map.get(&entity).copied().unwrap_or(entity) /// } -/// +/// /// fn set_mapped(&mut self, source: Entity, target: Entity) { /// self.map.insert(source, target); /// } @@ -143,12 +227,10 @@ impl EntityMapper for SceneEntityMapper<'_> { // this new entity reference is specifically designed to never represent any living entity let new = Entity::from_raw_and_generation( - self.dead_start.index(), - IdentifierMask::inc_masked_high_by(self.dead_start.generation, self.generations), + self.dead_start.row(), + self.dead_start.generation.after_versions(self.generations), ); - - // Prevent generations counter from being a greater value than HIGH_MASK. - self.generations = (self.generations + 1) & HIGH_MASK; + self.generations = self.generations.wrapping_add(1); self.map.insert(source, new); @@ -246,21 +328,19 @@ impl<'m> SceneEntityMapper<'m> { #[cfg(test)] mod tests { + use crate::{ - entity::{hash_map::EntityHashMap, Entity, EntityMapper, SceneEntityMapper}, + entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper}, world::World, }; #[test] fn entity_mapper() { - const FIRST_IDX: u32 = 1; - const SECOND_IDX: u32 = 2; - let mut map = EntityHashMap::default(); let mut world = World::new(); let mut mapper = SceneEntityMapper::new(&mut map, &mut world); - let mapped_ent = Entity::from_raw(FIRST_IDX); + let mapped_ent = Entity::from_raw_u32(1).unwrap(); let dead_ref = mapper.get_mapped(mapped_ent); assert_eq!( @@ -269,7 +349,7 @@ mod tests { "should persist the allocated mapping from the previous line" ); assert_eq!( - mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(), + mapper.get_mapped(Entity::from_raw_u32(2).unwrap()).index(), dead_ref.index(), "should re-use the same index for further dead refs" ); @@ -287,7 +367,7 @@ mod tests { let mut world = World::new(); let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| { - mapper.get_mapped(Entity::from_raw(0)) + mapper.get_mapped(Entity::from_raw_u32(0).unwrap()) }); // Next allocated entity should be a further generation on the same index diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 79c5f8c9e7..e8fb998fdc 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -39,16 +39,15 @@ mod clone_entities; mod entity_set; mod map_entities; -mod visit_entities; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; #[cfg(all(feature = "bevy_reflect", feature = "serialize"))] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; pub use clone_entities::*; +use derive_more::derive::Display; pub use entity_set::*; pub use map_entities::*; -pub use visit_entities::*; mod hash; pub use hash::*; @@ -56,26 +55,32 @@ pub use hash::*; pub mod hash_map; pub mod hash_set; +pub use hash_map::EntityHashMap; +pub use hash_set::EntityHashSet; + pub mod index_map; pub mod index_set; +pub use index_map::EntityIndexMap; +pub use index_set::EntityIndexSet; + pub mod unique_array; pub mod unique_slice; pub mod unique_vec; +use nonmax::NonMaxU32; +pub use unique_array::{UniqueEntityArray, UniqueEntityEquivalentArray}; +pub use unique_slice::{UniqueEntityEquivalentSlice, UniqueEntitySlice}; +pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec}; + use crate::{ archetype::{ArchetypeId, ArchetypeRow}, change_detection::MaybeLocation, - identifier::{ - error::IdentifierError, - kinds::IdKind, - masks::{IdentifierMask, HIGH_MASK}, - Identifier, - }, + component::Tick, storage::{SparseSetIndex, TableId, TableRow}, }; use alloc::vec::Vec; -use bevy_platform_support::sync::atomic::Ordering; +use bevy_platform::sync::atomic::Ordering; use core::{fmt, hash::Hash, mem, num::NonZero, panic::Location}; use log::warn; @@ -83,7 +88,7 @@ use log::warn; use serde::{Deserialize, Serialize}; #[cfg(target_has_atomic = "64")] -use bevy_platform_support::sync::atomic::AtomicI64 as AtomicIdCursor; +use bevy_platform::sync::atomic::AtomicI64 as AtomicIdCursor; #[cfg(target_has_atomic = "64")] type IdCursor = i64; @@ -91,13 +96,194 @@ type IdCursor = i64; /// do not. This fallback allows compilation using a 32-bit cursor instead, with /// the caveat that some conversions may fail (and panic) at runtime. #[cfg(not(target_has_atomic = "64"))] -use bevy_platform_support::sync::atomic::AtomicIsize as AtomicIdCursor; +use bevy_platform::sync::atomic::AtomicIsize as AtomicIdCursor; #[cfg(not(target_has_atomic = "64"))] type IdCursor = isize; +/// This represents the row or "index" of an [`Entity`] within the [`Entities`] table. +/// This is a lighter weight version of [`Entity`]. +/// +/// This is a unique identifier for an entity in the world. +/// This differs from [`Entity`] in that [`Entity`] is unique for all entities total (unless the [`Entity::generation`] wraps), +/// but this is only unique for entities that are active. +/// +/// This can be used over [`Entity`] to improve performance in some cases, +/// but improper use can cause this to identify a different entity than intended. +/// Use with caution. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(opaque))] +#[cfg_attr(feature = "bevy_reflect", reflect(Hash, PartialEq, Debug, Clone))] +#[repr(transparent)] +pub struct EntityRow(NonMaxU32); + +impl EntityRow { + const PLACEHOLDER: Self = Self(NonMaxU32::MAX); + + /// Constructs a new [`EntityRow`] from its index. + pub const fn new(index: NonMaxU32) -> Self { + Self(index) + } + + /// Gets the index of the entity. + #[inline(always)] + pub const fn index(self) -> u32 { + self.0.get() + } + + /// Gets some bits that represent this value. + /// The bits are opaque and should not be regarded as meaningful. + #[inline(always)] + const fn to_bits(self) -> u32 { + // SAFETY: NonMax is repr transparent. + unsafe { mem::transmute::(self.0) } + } + + /// Reconstruct an [`EntityRow`] previously destructured with [`EntityRow::to_bits`]. + /// + /// Only useful when applied to results from `to_bits` in the same instance of an application. + /// + /// # Panics + /// + /// This method will likely panic if given `u32` values that did not come from [`EntityRow::to_bits`]. + #[inline] + const fn from_bits(bits: u32) -> Self { + Self::try_from_bits(bits).expect("Attempted to initialize invalid bits as an entity row") + } + + /// Reconstruct an [`EntityRow`] previously destructured with [`EntityRow::to_bits`]. + /// + /// Only useful when applied to results from `to_bits` in the same instance of an application. + /// + /// This method is the fallible counterpart to [`EntityRow::from_bits`]. + #[inline(always)] + const fn try_from_bits(bits: u32) -> Option { + match NonZero::::new(bits) { + // SAFETY: NonMax and NonZero are repr transparent. + Some(underlying) => Some(Self(unsafe { + mem::transmute::, NonMaxU32>(underlying) + })), + None => None, + } + } +} + +impl SparseSetIndex for EntityRow { + #[inline] + fn sparse_set_index(&self) -> usize { + self.index() as usize + } + + #[inline] + fn get_sparse_set_index(value: usize) -> Self { + Self::from_bits(value as u32) + } +} + +/// This tracks different versions or generations of an [`EntityRow`]. +/// Importantly, this can wrap, meaning each generation is not necessarily unique per [`EntityRow`]. +/// +/// This should be treated as a opaque identifier, and its internal representation may be subject to change. +/// +/// # Ordering +/// +/// [`EntityGeneration`] implements [`Ord`]. +/// Generations that are later will be [`Greater`](core::cmp::Ordering::Greater) than earlier ones. +/// +/// ``` +/// # use bevy_ecs::entity::EntityGeneration; +/// assert!(EntityGeneration::FIRST < EntityGeneration::FIRST.after_versions(400)); +/// let (aliased, did_alias) = EntityGeneration::FIRST.after_versions(400).after_versions_and_could_alias(u32::MAX); +/// assert!(did_alias); +/// assert!(EntityGeneration::FIRST < aliased); +/// ``` +/// +/// Ordering will be incorrect for distant generations: +/// +/// ``` +/// # use bevy_ecs::entity::EntityGeneration; +/// // This ordering is wrong! +/// assert!(EntityGeneration::FIRST > EntityGeneration::FIRST.after_versions(400 + (1u32 << 31))); +/// ``` +/// +/// This strange behavior needed to account for aliasing. +/// +/// # Aliasing +/// +/// Internally [`EntityGeneration`] wraps a `u32`, so it can't represent *every* possible generation. +/// Eventually, generations can (and do) wrap or alias. +/// This can cause [`Entity`] and [`EntityGeneration`] values to be equal while still referring to different conceptual entities. +/// This can cause some surprising behavior: +/// +/// ``` +/// # use bevy_ecs::entity::EntityGeneration; +/// let (aliased, did_alias) = EntityGeneration::FIRST.after_versions(1u32 << 31).after_versions_and_could_alias(1u32 << 31); +/// assert!(did_alias); +/// assert!(EntityGeneration::FIRST == aliased); +/// ``` +/// +/// This can cause some unintended side effects. +/// See [`Entity`] docs for practical concerns and how to minimize any risks. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Display)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(opaque))] +#[cfg_attr(feature = "bevy_reflect", reflect(Hash, PartialEq, Debug, Clone))] +#[repr(transparent)] +pub struct EntityGeneration(u32); + +impl EntityGeneration { + /// Represents the first generation of an [`EntityRow`]. + pub const FIRST: Self = Self(0); + + /// Gets some bits that represent this value. + /// The bits are opaque and should not be regarded as meaningful. + #[inline(always)] + const fn to_bits(self) -> u32 { + self.0 + } + + /// Reconstruct an [`EntityGeneration`] previously destructured with [`EntityGeneration::to_bits`]. + /// + /// Only useful when applied to results from `to_bits` in the same instance of an application. + #[inline] + const fn from_bits(bits: u32) -> Self { + Self(bits) + } + + /// Returns the [`EntityGeneration`] that would result from this many more `versions` of the corresponding [`EntityRow`] from passing. + #[inline] + pub const fn after_versions(self, versions: u32) -> Self { + Self(self.0.wrapping_add(versions)) + } + + /// Identical to [`after_versions`](Self::after_versions) but also returns a `bool` indicating if, + /// after these `versions`, one such version could conflict with a previous one. + /// + /// If this happens, this will no longer uniquely identify a version of an [`EntityRow`]. + /// This is called entity aliasing. + #[inline] + pub const fn after_versions_and_could_alias(self, versions: u32) -> (Self, bool) { + let raw = self.0.overflowing_add(versions); + (Self(raw.0), raw.1) + } +} + +impl PartialOrd for EntityGeneration { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EntityGeneration { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let diff = self.0.wrapping_sub(other.0); + (1u32 << 31).cmp(&diff) + } +} + /// Lightweight identifier of an [entity](crate::entity). /// -/// The identifier is implemented using a [generational index]: a combination of an index and a generation. +/// The identifier is implemented using a [generational index]: a combination of an index ([`EntityRow`]) and a generation ([`EntityGeneration`]). /// This allows fast insertion after data removal in an array while minimizing loss of spatial locality. /// /// These identifiers are only valid on the [`World`] it's sourced from. Attempting to use an `Entity` to @@ -105,6 +291,19 @@ type IdCursor = isize; /// /// [generational index]: https://lucassardois.medium.com/generational-indices-guide-8e3c5f7fd594 /// +/// # Aliasing +/// +/// Once an entity is despawned, it ceases to exist. +/// However, its [`Entity`] id is still present, and may still be contained in some data. +/// This becomes problematic because it is possible for a later entity to be spawned at the exact same id! +/// If this happens, which is rare but very possible, it will be logged. +/// +/// Aliasing can happen without warning. +/// Holding onto a [`Entity`] id corresponding to an entity well after that entity was despawned can cause un-intuitive behavior for both ordering, and comparing in general. +/// To prevent these bugs, it is generally best practice to stop holding an [`Entity`] or [`EntityGeneration`] value as soon as you know it has been despawned. +/// If you must do otherwise, do not assume the [`Entity`] corresponds to the same conceptual entity it originally did. +/// See [`EntityGeneration`]'s docs for more information about aliasing and why it occurs. +/// /// # Stability warning /// For all intents and purposes, `Entity` should be treated as an opaque identifier. The internal bit /// representation is liable to change from release to release as are the behaviors or performance @@ -178,10 +377,10 @@ 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: NonZero, + row: EntityRow, + generation: EntityGeneration, #[cfg(target_endian = "big")] - index: u32, + row: EntityRow, } // By not short-circuiting in comparisons, we get better codegen. @@ -235,23 +434,15 @@ impl Hash for Entity { } } -#[deprecated( - note = "This is exclusively used with the now deprecated `Entities::alloc_at_without_replacement`." -)] -pub(crate) enum AllocAtWithoutReplacement { - Exists(EntityLocation), - DidNotExist, - ExistsWithWrongGeneration, -} - impl Entity { - /// Construct an [`Entity`] from a raw `index` value and a non-zero `generation` value. + /// Construct an [`Entity`] from a raw `row` value and a non-zero `generation` value. /// Ensure that the generation value is never greater than `0x7FFF_FFFF`. #[inline(always)] - pub(crate) const fn from_raw_and_generation(index: u32, generation: NonZero) -> Entity { - debug_assert!(generation.get() <= HIGH_MASK); - - Self { index, generation } + pub(crate) const fn from_raw_and_generation( + row: EntityRow, + generation: EntityGeneration, + ) -> Entity { + Self { row, generation } } /// An entity ID with a placeholder value. This may or may not correspond to an actual entity, @@ -288,9 +479,9 @@ impl Entity { /// } /// } /// ``` - pub const PLACEHOLDER: Self = Self::from_raw(u32::MAX); + pub const PLACEHOLDER: Self = Self::from_raw(EntityRow::PLACEHOLDER); - /// Creates a new entity ID with the specified `index` and a generation of 1. + /// Creates a new entity ID with the specified `row` and a generation of 1. /// /// # Note /// @@ -303,8 +494,19 @@ impl Entity { /// `Entity` lines up between instances, but instead insert a secondary identifier as /// a component. #[inline(always)] - pub const fn from_raw(index: u32) -> Entity { - Self::from_raw_and_generation(index, NonZero::::MIN) + pub const fn from_raw(row: EntityRow) -> Entity { + Self::from_raw_and_generation(row, EntityGeneration::FIRST) + } + + /// This is equivalent to [`from_raw`](Self::from_raw) except that it takes a `u32` instead of an [`EntityRow`]. + /// + /// Returns `None` if the row is `u32::MAX`. + #[inline(always)] + pub const fn from_raw_u32(row: u32) -> Option { + match NonMaxU32::new(row) { + Some(row) => Some(Self::from_raw(EntityRow::new(row))), + None => None, + } } /// Convert to a form convenient for passing outside of rust. @@ -315,7 +517,7 @@ impl Entity { /// No particular structure is guaranteed for the returned bits. #[inline(always)] pub const fn to_bits(self) -> u64 { - IdentifierMask::pack_into_u64(self.index, self.generation.get()) + self.row.to_bits() as u64 | ((self.generation.to_bits() as u64) << 32) } /// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`]. @@ -327,12 +529,10 @@ impl Entity { /// This method will likely panic if given `u64` values that did not come from [`Entity::to_bits`]. #[inline] pub const fn from_bits(bits: u64) -> Self { - // Construct an Identifier initially to extract the kind from. - let id = Self::try_from_bits(bits); - - match id { - Ok(entity) => entity, - Err(_) => panic!("Attempted to initialize invalid bits as an entity"), + if let Some(id) = Self::try_from_bits(bits) { + id + } else { + panic!("Attempted to initialize invalid bits as an entity") } } @@ -342,54 +542,43 @@ impl Entity { /// /// This method is the fallible counterpart to [`Entity::from_bits`]. #[inline(always)] - pub const fn try_from_bits(bits: u64) -> Result { - if let Ok(id) = Identifier::try_from_bits(bits) { - let kind = id.kind() as u8; + pub const fn try_from_bits(bits: u64) -> Option { + let raw_row = bits as u32; + let raw_gen = (bits >> 32) as u32; - if kind == (IdKind::Entity as u8) { - return Ok(Self { - index: id.low(), - generation: id.high(), - }); - } + if let Some(row) = EntityRow::try_from_bits(raw_row) { + Some(Self { + row, + generation: EntityGeneration::from_bits(raw_gen), + }) + } else { + None } - - Err(IdentifierError::InvalidEntityId(bits)) } /// Return a transiently unique identifier. + /// See also [`EntityRow`]. /// - /// No two simultaneously-live entities share the same index, but dead entities' indices may collide + /// No two simultaneously-live entities share the same row, but dead entities' indices may collide /// with both live and dead entities. Useful for compactly representing entities within a /// specific snapshot of the world, such as when serializing. #[inline] + pub const fn row(self) -> EntityRow { + self.row + } + + /// Equivalent to `self.row().index()`. See [`Self::row`] for details. + #[inline] pub const fn index(self) -> u32 { - self.index + self.row.index() } - /// Returns the generation of this Entity's index. The generation is incremented each time an - /// entity with a given index is despawned. This serves as a "count" of the number of times a - /// given index has been reused (index, generation) pairs uniquely identify a given Entity. + /// Returns the generation of this Entity's row. The generation is incremented each time an + /// entity with a given row is despawned. This serves as a "count" of the number of times a + /// given row has been reused (row, generation) pairs uniquely identify a given Entity. #[inline] - pub const fn generation(self) -> u32 { - // Mask so not to expose any flags - IdentifierMask::extract_value_from_high(self.generation.get()) - } -} - -impl TryFrom for Entity { - type Error = IdentifierError; - - #[inline] - fn try_from(value: Identifier) -> Result { - Self::try_from_bits(value.to_bits()) - } -} - -impl From for Identifier { - #[inline] - fn from(value: Entity) -> Self { - Identifier::from_bits(value.to_bits()) + pub const fn generation(self) -> EntityGeneration { + self.generation } } @@ -411,7 +600,8 @@ impl<'de> Deserialize<'de> for Entity { { use serde::de::Error; let id: u64 = Deserialize::deserialize(deserializer)?; - Entity::try_from_bits(id).map_err(D::Error::custom) + Entity::try_from_bits(id) + .ok_or_else(|| D::Error::custom("Attempting to deserialize an invalid entity.")) } } @@ -472,12 +662,12 @@ impl fmt::Display for Entity { impl SparseSetIndex for Entity { #[inline] fn sparse_set_index(&self) -> usize { - self.index() as usize + self.row().sparse_set_index() } #[inline] fn get_sparse_set_index(value: usize) -> Self { - Entity::from_raw(value as u32) + Entity::from_raw(EntityRow::get_sparse_set_index(value)) } } @@ -487,7 +677,7 @@ pub struct ReserveEntitiesIterator<'a> { meta: &'a [EntityMeta], // Reserved indices formerly in the freelist to hand out. - freelist_indices: core::slice::Iter<'a, u32>, + freelist_indices: core::slice::Iter<'a, EntityRow>, // New Entity indices to hand out, outside the range of meta.len(). new_indices: core::ops::Range, @@ -499,10 +689,16 @@ impl<'a> Iterator for ReserveEntitiesIterator<'a> { fn next(&mut self) -> Option { self.freelist_indices .next() - .map(|&index| { - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + .map(|&row| { + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) + }) + .or_else(|| { + self.new_indices.next().map(|index| { + // SAFETY: This came from an exclusive range so the max can't be hit. + let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(index)) }; + Entity::from_raw(row) + }) }) - .or_else(|| self.new_indices.next().map(Entity::from_raw)) } fn size_hint(&self) -> (usize, Option) { @@ -569,7 +765,7 @@ pub struct Entities { /// [`reserve_entity`]: Entities::reserve_entity /// [`reserve_entities`]: Entities::reserve_entities /// [`flush`]: Entities::flush - pending: Vec, + pending: Vec, free_cursor: AtomicIdCursor, } @@ -645,17 +841,21 @@ impl Entities { let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed); if n > 0 { // Allocate from the freelist. - let index = self.pending[(n - 1) as usize]; - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + let row = self.pending[(n - 1) as usize]; + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) } else { // Grab a new ID, outside the range of `meta.len()`. `flush()` must // eventually be called to make it valid. // // As `self.free_cursor` goes more and more negative, we return IDs farther // and farther beyond `meta.len()`. - Entity::from_raw( - u32::try_from(self.meta.len() as IdCursor - n).expect("too many entities"), - ) + let raw = self.meta.len() as IdCursor - n; + if raw >= u32::MAX as IdCursor { + panic!("too many entities"); + } + // SAFETY: We just checked the bounds + let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(raw as u32)) }; + Entity::from_raw(row) } } @@ -670,96 +870,20 @@ impl Entities { /// Allocate an entity ID directly. pub fn alloc(&mut self) -> Entity { self.verify_flushed(); - if let Some(index) = self.pending.pop() { + if let Some(row) = self.pending.pop() { let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) } else { - let index = u32::try_from(self.meta.len()).expect("too many entities"); + let index = u32::try_from(self.meta.len()) + .ok() + .and_then(NonMaxU32::new) + .expect("too many entities"); self.meta.push(EntityMeta::EMPTY); - Entity::from_raw(index) + Entity::from_raw(EntityRow::new(index)) } } - /// Allocate a specific entity ID, overwriting its generation. - /// - /// Returns the location of the entity currently using the given ID, if any. Location should be - /// written immediately. - #[deprecated( - note = "This can cause extreme performance problems when used after freeing a large number of entities and requesting an arbitrary entity. See #18054 on GitHub." - )] - pub fn alloc_at(&mut self, entity: Entity) -> Option { - self.verify_flushed(); - - let loc = if entity.index() as usize >= self.meta.len() { - self.pending - .extend((self.meta.len() as u32)..entity.index()); - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - self.meta - .resize(entity.index() as usize + 1, EntityMeta::EMPTY); - None - } else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) { - self.pending.swap_remove(index); - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - None - } else { - Some(mem::replace( - &mut self.meta[entity.index() as usize].location, - EntityMeta::EMPTY.location, - )) - }; - - self.meta[entity.index() as usize].generation = entity.generation; - - loc - } - - /// Allocate a specific entity ID, overwriting its generation. - /// - /// Returns the location of the entity currently using the given ID, if any. - #[deprecated( - note = "This can cause extreme performance problems when used after freeing a large number of entities and requesting an arbitrary entity. See #18054 on GitHub." - )] - #[expect( - deprecated, - reason = "We need to support `AllocAtWithoutReplacement` for now." - )] - pub(crate) fn alloc_at_without_replacement( - &mut self, - entity: Entity, - ) -> AllocAtWithoutReplacement { - self.verify_flushed(); - - let result = if entity.index() as usize >= self.meta.len() { - self.pending - .extend((self.meta.len() as u32)..entity.index()); - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - self.meta - .resize(entity.index() as usize + 1, EntityMeta::EMPTY); - AllocAtWithoutReplacement::DidNotExist - } else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) { - self.pending.swap_remove(index); - let new_free_cursor = self.pending.len() as IdCursor; - *self.free_cursor.get_mut() = new_free_cursor; - AllocAtWithoutReplacement::DidNotExist - } else { - let current_meta = &self.meta[entity.index() as usize]; - if current_meta.location.archetype_id == ArchetypeId::INVALID { - AllocAtWithoutReplacement::DidNotExist - } else if current_meta.generation == entity.generation { - AllocAtWithoutReplacement::Exists(current_meta.location) - } else { - return AllocAtWithoutReplacement::ExistsWithWrongGeneration; - } - }; - - self.meta[entity.index() as usize].generation = entity.generation; - result - } - /// Destroy an entity, allowing it to be reused. /// /// Must not be called while reserved entities are awaiting `flush()`. @@ -771,18 +895,18 @@ impl Entities { return None; } - meta.generation = IdentifierMask::inc_masked_high_by(meta.generation, 1); - - if meta.generation == NonZero::::MIN { + let (new_generation, aliased) = meta.generation.after_versions_and_could_alias(1); + meta.generation = new_generation; + if aliased { warn!( "Entity({}) generation wrapped on Entities::free, aliasing may occur", - entity.index + entity.row() ); } let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location); - self.pending.push(entity.index()); + self.pending.push(entity.row()); let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; @@ -814,7 +938,7 @@ impl Entities { // This will return false for entities which have been freed, even if // not reallocated since the generation is incremented in `free` pub fn contains(&self, entity: Entity) -> bool { - self.resolve_from_id(entity.index()) + self.resolve_from_id(entity.row()) .is_some_and(|e| e.generation() == entity.generation()) } @@ -841,8 +965,8 @@ impl Entities { } } - /// Updates the location of an [`Entity`]. This must be called when moving the components of - /// the entity around in storage. + /// Updates the location of an [`Entity`]. + /// This must be called when moving the components of the existing entity around in storage. /// /// # Safety /// - `index` must be a valid entity index. @@ -855,6 +979,17 @@ impl Entities { meta.location = location; } + /// Mark an [`Entity`] as spawned or despawned in the given tick. + /// + /// # Safety + /// - `index` must be a valid entity index. + #[inline] + pub(crate) unsafe fn mark_spawn_despawn(&mut self, index: u32, by: MaybeLocation, at: Tick) { + // SAFETY: Caller guarantees that `index` a valid entity index + let meta = unsafe { self.meta.get_unchecked_mut(index as usize) }; + meta.spawned_or_despawned = SpawnedOrDespawned { by, at }; + } + /// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this /// `index` will count `generation` starting from the prior `generation` + the specified /// value + 1. @@ -867,7 +1002,7 @@ impl Entities { let meta = &mut self.meta[index as usize]; if meta.location.archetype_id == ArchetypeId::INVALID { - meta.generation = IdentifierMask::inc_masked_high_by(meta.generation, generations); + meta.generation = meta.generation.after_versions(generations); true } else { false @@ -880,17 +1015,17 @@ impl Entities { /// Note: This method may return [`Entities`](Entity) which are currently free /// Note that [`contains`](Entities::contains) will correctly return false for freed /// entities, since it checks the generation - pub fn resolve_from_id(&self, index: u32) -> Option { - let idu = index as usize; + pub fn resolve_from_id(&self, row: EntityRow) -> Option { + let idu = row.index() as usize; if let Some(&EntityMeta { generation, .. }) = self.meta.get(idu) { - Some(Entity::from_raw_and_generation(index, generation)) + Some(Entity::from_raw_and_generation(row, generation)) } else { // `id` is outside of the meta list - check whether it is reserved but not yet flushed. let free_cursor = self.free_cursor.load(Ordering::Relaxed); // If this entity was manually created, then free_cursor might be positive // Returning None handles that case correctly let num_pending = usize::try_from(-free_cursor).ok()?; - (idu < self.meta.len() + num_pending).then_some(Entity::from_raw(index)) + (idu < self.meta.len() + num_pending).then_some(Entity::from_raw(row)) } } @@ -908,7 +1043,12 @@ impl Entities { /// /// Note: freshly-allocated entities (ones which don't come from the pending list) are guaranteed /// to be initialized with the invalid archetype. - pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) { + pub unsafe fn flush( + &mut self, + mut init: impl FnMut(Entity, &mut EntityLocation), + by: MaybeLocation, + at: Tick, + ) { let free_cursor = self.free_cursor.get_mut(); let current_free_cursor = *free_cursor; @@ -919,34 +1059,42 @@ impl Entities { let new_meta_len = old_meta_len + -current_free_cursor as usize; self.meta.resize(new_meta_len, EntityMeta::EMPTY); for (index, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) { + // SAFETY: the index is less than the meta length, which can not exceeded u32::MAX + let row = EntityRow::new(unsafe { NonMaxU32::new_unchecked(index as u32) }); init( - Entity::from_raw_and_generation(index as u32, meta.generation), + Entity::from_raw_and_generation(row, meta.generation), &mut meta.location, ); + meta.spawned_or_despawned = SpawnedOrDespawned { by, at }; } *free_cursor = 0; 0 }; - for index in self.pending.drain(new_free_cursor..) { - let meta = &mut self.meta[index as usize]; + for row in self.pending.drain(new_free_cursor..) { + let meta = &mut self.meta[row.index() as usize]; init( - Entity::from_raw_and_generation(index, meta.generation), + Entity::from_raw_and_generation(row, meta.generation), &mut meta.location, ); + meta.spawned_or_despawned = SpawnedOrDespawned { by, at }; } } /// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return `None` /// unless they are later populated with a valid archetype. - pub fn flush_as_invalid(&mut self) { + pub fn flush_as_invalid(&mut self, by: MaybeLocation, at: Tick) { // SAFETY: as per `flush` safety docs, the archetype id can be set to [`ArchetypeId::INVALID`] if // the [`Entity`] has not been assigned to an [`Archetype`][crate::archetype::Archetype], which is the case here unsafe { - self.flush(|_entity, location| { - location.archetype_id = ArchetypeId::INVALID; - }); + self.flush( + |_entity, location| { + location.archetype_id = ArchetypeId::INVALID; + }, + by, + at, + ); } } @@ -993,37 +1141,74 @@ impl Entities { self.len() == 0 } - /// Sets the source code location from which this entity has last been spawned - /// or despawned. - #[inline] - pub(crate) fn set_spawned_or_despawned_by(&mut self, index: u32, caller: MaybeLocation) { - caller.map(|caller| { - let meta = self - .meta - .get_mut(index as usize) - .expect("Entity index invalid"); - meta.spawned_or_despawned_by = MaybeLocation::new(Some(caller)); - }); - } - - /// Returns the source code location from which this entity has last been spawned - /// or despawned. Returns `None` if its index has been reused by another entity + /// Try to get the source code location from which this entity has last been + /// spawned, despawned or flushed. + /// + /// Returns `None` if its index has been reused by another entity /// or if this entity has never existed. pub fn entity_get_spawned_or_despawned_by( &self, entity: Entity, ) -> MaybeLocation>> { MaybeLocation::new_with_flattened(|| { - self.meta - .get(entity.index() as usize) - .filter(|meta| - // Generation is incremented immediately upon despawn - (meta.generation == entity.generation) - || (meta.location.archetype_id == ArchetypeId::INVALID) - && (meta.generation == IdentifierMask::inc_masked_high_by(entity.generation, 1))) - .map(|meta| meta.spawned_or_despawned_by) + self.entity_get_spawned_or_despawned(entity) + .map(|spawned_or_despawned| spawned_or_despawned.by) }) - .map(Option::flatten) + } + + /// Try to get the [`Tick`] at which this entity has last been + /// spawned, despawned or flushed. + /// + /// Returns `None` if its index has been reused by another entity or if this entity + /// has never been spawned. + pub fn entity_get_spawned_or_despawned_at(&self, entity: Entity) -> Option { + self.entity_get_spawned_or_despawned(entity) + .map(|spawned_or_despawned| spawned_or_despawned.at) + } + + /// Try to get the [`SpawnedOrDespawned`] related to the entity's last spawn, + /// despawn or flush. + /// + /// Returns `None` if its index has been reused by another entity or if + /// this entity has never been spawned. + #[inline] + fn entity_get_spawned_or_despawned(&self, entity: Entity) -> Option { + self.meta + .get(entity.index() as usize) + .filter(|meta| + // Generation is incremented immediately upon despawn + (meta.generation == entity.generation) + || (meta.location.archetype_id == ArchetypeId::INVALID) + && (meta.generation == entity.generation.after_versions(1))) + .map(|meta| meta.spawned_or_despawned) + } + + /// Returns the source code location from which this entity has last been spawned + /// or despawned and the Tick of when that happened. + /// + /// # Safety + /// + /// The entity index must belong to an entity that is currently alive or, if it + /// despawned, was not overwritten by a new entity of the same index. + #[inline] + pub(crate) unsafe fn entity_get_spawned_or_despawned_unchecked( + &self, + entity: Entity, + ) -> (MaybeLocation, Tick) { + // SAFETY: caller ensures entity is allocated + let meta = unsafe { self.meta.get_unchecked(entity.index() as usize) }; + (meta.spawned_or_despawned.by, meta.spawned_or_despawned.at) + } + + #[inline] + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + for meta in &mut self.meta { + if meta.generation != EntityGeneration::FIRST + || meta.location.archetype_id != ArchetypeId::INVALID + { + meta.spawned_or_despawned.at.check_tick(change_tick); + } + } } /// Constructs a message explaining why an entity does not exist, if known. @@ -1081,20 +1266,29 @@ impl fmt::Display for EntityDoesNotExistDetails { #[derive(Copy, Clone, Debug)] struct EntityMeta { - /// The current generation of the [`Entity`]. - pub generation: NonZero, - /// The current location of the [`Entity`] + /// The current [`EntityGeneration`] of the [`EntityRow`]. + pub generation: EntityGeneration, + /// The current location of the [`EntityRow`]. pub location: EntityLocation, - /// Location of the last spawn or despawn of this entity - spawned_or_despawned_by: MaybeLocation>>, + /// Location and tick of the last spawn, despawn or flush of this entity. + spawned_or_despawned: SpawnedOrDespawned, +} + +#[derive(Copy, Clone, Debug)] +struct SpawnedOrDespawned { + by: MaybeLocation, + at: Tick, } impl EntityMeta { /// meta for **pending entity** const EMPTY: EntityMeta = EntityMeta { - generation: NonZero::::MIN, + generation: EntityGeneration::FIRST, location: EntityLocation::INVALID, - spawned_or_despawned_by: MaybeLocation::new(None), + spawned_or_despawned: SpawnedOrDespawned { + by: MaybeLocation::caller(), + at: Tick::new(0), + }, }; } @@ -1144,9 +1338,14 @@ mod tests { #[test] fn entity_bits_roundtrip() { + let r = EntityRow::new(NonMaxU32::new(0xDEADBEEF).unwrap()); + assert_eq!(EntityRow::from_bits(r.to_bits()), r); + // Generation cannot be greater than 0x7FFF_FFFF else it will be an invalid Entity id - let e = - Entity::from_raw_and_generation(0xDEADBEEF, NonZero::::new(0x5AADF00D).unwrap()); + let e = Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(0xDEADBEEF).unwrap()), + EntityGeneration::from_bits(0x5AADF00D), + ); assert_eq!(Entity::from_bits(e.to_bits()), e); } @@ -1155,7 +1354,7 @@ mod tests { let mut e = Entities::new(); e.reserve_entity(); // SAFETY: entity_location is left invalid - unsafe { e.flush(|_, _| {}) }; + unsafe { e.flush(|_, _| {}, MaybeLocation::caller(), Tick::default()) }; assert_eq!(e.len(), 1); } @@ -1168,9 +1367,13 @@ mod tests { // SAFETY: entity_location is left invalid unsafe { - entities.flush(|_entity, _location| { - // do nothing ... leaving entity location invalid - }); + entities.flush( + |_entity, _location| { + // do nothing ... leaving entity location invalid + }, + MaybeLocation::caller(), + Tick::default(), + ); }; assert!(entities.contains(e)); @@ -1179,18 +1382,20 @@ mod tests { #[test] fn entity_const() { - const C1: Entity = Entity::from_raw(42); + const C1: Entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); assert_eq!(42, C1.index()); - assert_eq!(1, C1.generation()); + assert_eq!(0, C1.generation().to_bits()); const C2: Entity = Entity::from_bits(0x0000_00ff_0000_00cc); - assert_eq!(0x0000_00cc, C2.index()); - assert_eq!(0x0000_00ff, C2.generation()); + assert_eq!(!0x0000_00cc, C2.index()); + assert_eq!(0x0000_00ff, C2.generation().to_bits()); - const C3: u32 = Entity::from_raw(33).index(); + const C3: u32 = Entity::from_raw(EntityRow::new(NonMaxU32::new(33).unwrap())).index(); assert_eq!(33, C3); - const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation(); + const C4: u32 = Entity::from_bits(0x00dd_00ff_1111_1111) + .generation() + .to_bits(); assert_eq!(0x00dd_00ff, C4); } @@ -1216,7 +1421,7 @@ mod tests { // The very next entity allocated should be a further generation on the same index let next_entity = entities.alloc(); assert_eq!(next_entity.index(), entity.index()); - assert!(next_entity.generation() > entity.generation() + GENERATIONS); + assert!(next_entity.generation() > entity.generation().after_versions(GENERATIONS)); } #[test] @@ -1226,65 +1431,139 @@ mod tests { )] fn entity_comparison() { assert_eq!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(789) + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(789) + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(456, NonZero::::new(123).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(456).unwrap()), + EntityGeneration::from_bits(123) + ) ); // ordering is by generation then by index assert!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - >= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) >= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) ); assert!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - <= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) <= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) ); assert!( - !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - < Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) + !(Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + )) ); assert!( - !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - > Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) + !(Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + EntityGeneration::from_bits(456) + )) ); assert!( - Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) - < Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(9).unwrap()), + EntityGeneration::from_bits(1) + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(9) + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) - > Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(9) + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(9).unwrap()), + EntityGeneration::from_bits(1) + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) - < Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(1) + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + EntityGeneration::from_bits(1) + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) - <= Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(1) + ) >= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + EntityGeneration::from_bits(1) + ) ); assert!( - Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) - > Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + EntityGeneration::from_bits(2) + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(2) + ) ); assert!( - Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) - >= Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + EntityGeneration::from_bits(2) + ) <= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + EntityGeneration::from_bits(2) + ) ); } @@ -1296,12 +1575,16 @@ mod tests { let hash = EntityHash; let first_id = 0xC0FFEE << 8; - let first_hash = hash.hash_one(Entity::from_raw(first_id)); + let first_hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(first_id).unwrap(), + ))); for i in 1..=255 { let id = first_id + i; - let hash = hash.hash_one(Entity::from_raw(id)); - assert_eq!(hash.wrapping_sub(first_hash) as u32, i); + let hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(id).unwrap(), + ))); + assert_eq!(first_hash.wrapping_sub(hash) as u32, i); } } @@ -1312,34 +1595,38 @@ mod tests { let hash = EntityHash; let first_id = 0xC0FFEE; - let first_hash = hash.hash_one(Entity::from_raw(first_id)) >> 57; + let first_hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(first_id).unwrap(), + ))) >> 57; for bit in 0..u32::BITS { let id = first_id ^ (1 << bit); - let hash = hash.hash_one(Entity::from_raw(id)) >> 57; + let hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(id).unwrap(), + ))) >> 57; assert_ne!(hash, first_hash); } } #[test] fn entity_debug() { - let entity = Entity::from_raw(42); - let string = format!("{:?}", entity); - assert_eq!(string, "42v1#4294967338"); + let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); + let string = format!("{entity:?}"); + assert_eq!(string, "42v0#4294967253"); let entity = Entity::PLACEHOLDER; - let string = format!("{:?}", entity); + let string = format!("{entity:?}"); assert_eq!(string, "PLACEHOLDER"); } #[test] fn entity_display() { - let entity = Entity::from_raw(42); - let string = format!("{}", entity); - assert_eq!(string, "42v1"); + let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); + let string = format!("{entity}"); + assert_eq!(string, "42v0"); let entity = Entity::PLACEHOLDER; - let string = format!("{}", entity); + let string = format!("{entity}"); assert_eq!(string, "PLACEHOLDER"); } } diff --git a/crates/bevy_ecs/src/entity/unique_array.rs b/crates/bevy_ecs/src/entity/unique_array.rs index a87e77676b..ce31e55448 100644 --- a/crates/bevy_ecs/src/entity/unique_array.rs +++ b/crates/bevy_ecs/src/entity/unique_array.rs @@ -18,22 +18,29 @@ use alloc::{ vec::Vec, }; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use super::{ - unique_slice::{self, UniqueEntitySlice}, - Entity, TrustedEntityBorrow, UniqueEntityIter, + unique_slice::{self, UniqueEntityEquivalentSlice}, + Entity, EntityEquivalent, UniqueEntityIter, }; /// An array that contains only unique entities. /// -/// It can be obtained through certain methods on [`UniqueEntitySlice`], +/// It can be obtained through certain methods on [`UniqueEntityEquivalentSlice`], /// and some [`TryFrom`] implementations. +/// +/// When `T` is [`Entity`], use [`UniqueEntityArray`]. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct UniqueEntityArray([T; N]); +pub struct UniqueEntityEquivalentArray([T; N]); -impl UniqueEntityArray { - /// Constructs a `UniqueEntityArray` from a [`[T; N]`] unsafely. +/// An array that contains only unique [`Entity`]. +/// +/// This is the default case of a [`UniqueEntityEquivalentArray`]. +pub type UniqueEntityArray = UniqueEntityEquivalentArray; + +impl UniqueEntityEquivalentArray { + /// Constructs a `UniqueEntityEquivalentArray` from a [`[T; N]`] unsafely. /// /// # Safety /// @@ -42,61 +49,61 @@ impl UniqueEntityArray { Self(array) } - /// Constructs a `&UniqueEntityArray` from a [`&[T; N]`] unsafely. + /// Constructs a `&UniqueEntityEquivalentArray` from a [`&[T; N]`] unsafely. /// /// # Safety /// /// `array` must contain only unique elements. pub const unsafe fn from_array_ref_unchecked(array: &[T; N]) -> &Self { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { &*(ptr::from_ref(array).cast()) } } - /// Constructs a `Box` from a [`Box<[T; N]>`] unsafely. + /// Constructs a `Box` from a [`Box<[T; N]>`] unsafely. /// /// # Safety /// /// `array` must contain only unique elements. pub unsafe fn from_boxed_array_unchecked(array: Box<[T; N]>) -> Box { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Box::from_raw(Box::into_raw(array).cast()) } } /// Casts `self` into the inner array. pub fn into_boxed_inner(self: Box) -> Box<[T; N]> { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Box::from_raw(Box::into_raw(self).cast()) } } - /// Constructs a `Arc` from a [`Arc<[T; N]>`] unsafely. + /// Constructs a `Arc` from a [`Arc<[T; N]>`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub unsafe fn from_arc_array_unchecked(slice: Arc<[T; N]>) -> Arc { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Arc::from_raw(Arc::into_raw(slice).cast()) } } /// Casts `self` to the inner array. pub fn into_arc_inner(this: Arc) -> Arc<[T; N]> { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Arc::from_raw(Arc::into_raw(this).cast()) } } - // Constructs a `Rc` from a [`Rc<[T; N]>`] unsafely. + // Constructs a `Rc` from a [`Rc<[T; N]>`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub unsafe fn from_rc_array_unchecked(slice: Rc<[T; N]>) -> Rc { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Rc::from_raw(Rc::into_raw(slice).cast()) } } /// Casts `self` to the inner array. pub fn into_rc_inner(self: Rc) -> Rc<[T; N]> { - // SAFETY: UniqueEntityArray is a transparent wrapper around [T; N]. + // SAFETY: UniqueEntityEquivalentArray is a transparent wrapper around [T; N]. unsafe { Rc::from_raw(Rc::into_raw(self).cast()) } } @@ -111,49 +118,51 @@ impl UniqueEntityArray { } /// Returns a slice containing the entire array. Equivalent to `&s[..]`. - pub const fn as_slice(&self) -> &UniqueEntitySlice { + pub const fn as_slice(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original array are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.as_slice()) } } /// Returns a mutable slice containing the entire array. Equivalent to /// `&mut s[..]`. - pub fn as_mut_slice(&mut self) -> &mut UniqueEntitySlice { + pub fn as_mut_slice(&mut self) -> &mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original array are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.as_mut_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.as_mut_slice()) } } /// Borrows each element and returns an array of references with the same /// size as `self`. /// /// Equivalent to [`[T; N]::as_ref`](array::each_ref). - pub fn each_ref(&self) -> UniqueEntityArray { - UniqueEntityArray(self.0.each_ref()) + pub fn each_ref(&self) -> UniqueEntityEquivalentArray<&T, N> { + UniqueEntityEquivalentArray(self.0.each_ref()) } } -impl Deref for UniqueEntityArray { - type Target = UniqueEntitySlice; +impl Deref for UniqueEntityEquivalentArray { + type Target = UniqueEntityEquivalentSlice; fn deref(&self) -> &Self::Target { // SAFETY: All elements in the original array are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(&self.0) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(&self.0) } } } -impl DerefMut for UniqueEntityArray { +impl DerefMut for UniqueEntityEquivalentArray { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: All elements in the original array are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(&mut self.0) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) } } } -impl Default for UniqueEntityArray<0, T> { +impl Default for UniqueEntityEquivalentArray { fn default() -> Self { Self(Default::default()) } } -impl<'a, T: TrustedEntityBorrow, const N: usize> IntoIterator for &'a UniqueEntityArray { +impl<'a, T: EntityEquivalent, const N: usize> IntoIterator + for &'a UniqueEntityEquivalentArray +{ type Item = &'a T; type IntoIter = unique_slice::Iter<'a, T>; @@ -164,7 +173,7 @@ impl<'a, T: TrustedEntityBorrow, const N: usize> IntoIterator for &'a UniqueEnti } } -impl IntoIterator for UniqueEntityArray { +impl IntoIterator for UniqueEntityEquivalentArray { type Item = T; type IntoIter = IntoIter; @@ -175,354 +184,376 @@ impl IntoIterator for UniqueEntityArray< } } -impl AsRef> - for UniqueEntityArray +impl AsRef> + for UniqueEntityEquivalentArray { - fn as_ref(&self) -> &UniqueEntitySlice { + fn as_ref(&self) -> &UniqueEntityEquivalentSlice { self } } -impl AsMut> - for UniqueEntityArray +impl AsMut> + for UniqueEntityEquivalentArray { - fn as_mut(&mut self) -> &mut UniqueEntitySlice { + fn as_mut(&mut self) -> &mut UniqueEntityEquivalentSlice { self } } -impl Borrow> - for UniqueEntityArray +impl Borrow> + for UniqueEntityEquivalentArray { - fn borrow(&self) -> &UniqueEntitySlice { + fn borrow(&self) -> &UniqueEntityEquivalentSlice { self } } -impl BorrowMut> - for UniqueEntityArray +impl BorrowMut> + for UniqueEntityEquivalentArray { - fn borrow_mut(&mut self) -> &mut UniqueEntitySlice { + fn borrow_mut(&mut self) -> &mut UniqueEntityEquivalentSlice { self } } -impl Index<(Bound, Bound)> - for UniqueEntityArray +impl Index<(Bound, Bound)> + for UniqueEntityEquivalentArray { - type Output = UniqueEntitySlice; + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: (Bound, Bound)) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityArray { - type Output = UniqueEntitySlice; +impl Index> + for UniqueEntityEquivalentArray +{ + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: Range) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityArray { - type Output = UniqueEntitySlice; +impl Index> + for UniqueEntityEquivalentArray +{ + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeFrom) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index for UniqueEntityArray { - type Output = UniqueEntitySlice; +impl Index for UniqueEntityEquivalentArray { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeFull) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> - for UniqueEntityArray +impl Index> + for UniqueEntityEquivalentArray { - type Output = UniqueEntitySlice; + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeInclusive) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityArray { - type Output = UniqueEntitySlice; +impl Index> + for UniqueEntityEquivalentArray +{ + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeTo) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> - for UniqueEntityArray +impl Index> + for UniqueEntityEquivalentArray { - type Output = UniqueEntitySlice; + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeToInclusive) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index for UniqueEntityArray { +impl Index for UniqueEntityEquivalentArray { type Output = T; fn index(&self, key: usize) -> &T { self.0.index(key) } } -impl IndexMut<(Bound, Bound)> - for UniqueEntityArray +impl IndexMut<(Bound, Bound)> + for UniqueEntityEquivalentArray { fn index_mut(&mut self, key: (Bound, Bound)) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityArray { +impl IndexMut> + for UniqueEntityEquivalentArray +{ fn index_mut(&mut self, key: Range) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> - for UniqueEntityArray +impl IndexMut> + for UniqueEntityEquivalentArray { fn index_mut(&mut self, key: RangeFrom) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut for UniqueEntityArray { +impl IndexMut + for UniqueEntityEquivalentArray +{ fn index_mut(&mut self, key: RangeFull) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> - for UniqueEntityArray +impl IndexMut> + for UniqueEntityEquivalentArray { fn index_mut(&mut self, key: RangeInclusive) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityArray { +impl IndexMut> + for UniqueEntityEquivalentArray +{ fn index_mut(&mut self, key: RangeTo) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> - for UniqueEntityArray +impl IndexMut> + for UniqueEntityEquivalentArray { fn index_mut(&mut self, key: RangeToInclusive) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl From<&[T; 1]> for UniqueEntityArray<1, T> { +impl From<&[T; 1]> for UniqueEntityEquivalentArray { fn from(value: &[T; 1]) -> Self { Self(value.clone()) } } -impl From<&[T; 0]> for UniqueEntityArray<0, T> { +impl From<&[T; 0]> for UniqueEntityEquivalentArray { fn from(value: &[T; 0]) -> Self { Self(value.clone()) } } -impl From<&mut [T; 1]> for UniqueEntityArray<1, T> { +impl From<&mut [T; 1]> for UniqueEntityEquivalentArray { fn from(value: &mut [T; 1]) -> Self { Self(value.clone()) } } -impl From<&mut [T; 0]> for UniqueEntityArray<0, T> { +impl From<&mut [T; 0]> for UniqueEntityEquivalentArray { fn from(value: &mut [T; 0]) -> Self { Self(value.clone()) } } -impl From<[T; 1]> for UniqueEntityArray<1, T> { +impl From<[T; 1]> for UniqueEntityEquivalentArray { fn from(value: [T; 1]) -> Self { Self(value) } } -impl From<[T; 0]> for UniqueEntityArray<0, T> { +impl From<[T; 0]> for UniqueEntityEquivalentArray { fn from(value: [T; 0]) -> Self { Self(value) } } -impl From> for (T,) { - fn from(array: UniqueEntityArray<1, T>) -> Self { +impl From> for (T,) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T) { - fn from(array: UniqueEntityArray<2, T>) -> Self { +impl From> for (T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T) { - fn from(array: UniqueEntityArray<3, T>) -> Self { +impl From> for (T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T) { - fn from(array: UniqueEntityArray<4, T>) -> Self { +impl From> for (T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T) { - fn from(array: UniqueEntityArray<5, T>) -> Self { +impl From> for (T, T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<6, T>) -> Self { +impl From> for (T, T, T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<7, T>) -> Self { +impl From> for (T, T, T, T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<8, T>) -> Self { +impl From> for (T, T, T, T, T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<9, T>) -> Self { +impl From> for (T, T, T, T, T, T, T, T, T) { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<10, T>) -> Self { +impl From> + for (T, T, T, T, T, T, T, T, T, T) +{ + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for (T, T, T, T, T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<11, T>) -> Self { +impl From> + for (T, T, T, T, T, T, T, T, T, T, T) +{ + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> +impl From> for (T, T, T, T, T, T, T, T, T, T, T, T) { - fn from(array: UniqueEntityArray<12, T>) -> Self { + fn from(array: UniqueEntityEquivalentArray) -> Self { Self::from(array.into_inner()) } } -impl From> for BTreeSet { - fn from(value: UniqueEntityArray) -> Self { +impl From> + for BTreeSet +{ + fn from(value: UniqueEntityEquivalentArray) -> Self { BTreeSet::from(value.0) } } -impl From> for BinaryHeap { - fn from(value: UniqueEntityArray) -> Self { +impl From> + for BinaryHeap +{ + fn from(value: UniqueEntityEquivalentArray) -> Self { BinaryHeap::from(value.0) } } -impl From> for LinkedList { - fn from(value: UniqueEntityArray) -> Self { +impl From> + for LinkedList +{ + fn from(value: UniqueEntityEquivalentArray) -> Self { LinkedList::from(value.0) } } -impl From> for Vec { - fn from(value: UniqueEntityArray) -> Self { +impl From> for Vec { + fn from(value: UniqueEntityEquivalentArray) -> Self { Vec::from(value.0) } } -impl From> for VecDeque { - fn from(value: UniqueEntityArray) -> Self { +impl From> for VecDeque { + fn from(value: UniqueEntityEquivalentArray) -> Self { VecDeque::from(value.0) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq<&UniqueEntitySlice> for UniqueEntityArray +impl, U: EntityEquivalent, const N: usize> + PartialEq<&UniqueEntityEquivalentSlice> for UniqueEntityEquivalentArray { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.0.eq(&other.as_inner()) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq> for UniqueEntityArray +impl, U: EntityEquivalent, const N: usize> + PartialEq> for UniqueEntityEquivalentArray { - fn eq(&self, other: &UniqueEntitySlice) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentSlice) -> bool { self.0.eq(other.as_inner()) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&UniqueEntityArray> - for Vec +impl, U: EntityEquivalent, const N: usize> + PartialEq<&UniqueEntityEquivalentArray> for Vec { - fn eq(&self, other: &&UniqueEntityArray) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentArray) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&UniqueEntityArray> - for VecDeque +impl, U: EntityEquivalent, const N: usize> + PartialEq<&UniqueEntityEquivalentArray> for VecDeque { - fn eq(&self, other: &&UniqueEntityArray) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentArray) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq<&mut UniqueEntityArray> for VecDeque +impl, U: EntityEquivalent, const N: usize> + PartialEq<&mut UniqueEntityEquivalentArray> for VecDeque { - fn eq(&self, other: &&mut UniqueEntityArray) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentArray) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq> - for Vec +impl, U: EntityEquivalent, const N: usize> + PartialEq> for Vec { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq> - for VecDeque +impl, U: EntityEquivalent, const N: usize> + PartialEq> for VecDeque { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.eq(&other.0) } } @@ -532,21 +563,25 @@ impl, U: TrustedEntityBorrow, const N: usize> PartialEq = UniqueEntityIter>; -impl UniqueEntityIter> { +impl UniqueEntityIter> { /// Returns an immutable slice of all elements that have not been yielded /// yet. /// /// Equivalent to [`array::IntoIter::as_slice`]. - pub fn as_slice(&self) -> &UniqueEntitySlice { + pub fn as_slice(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.as_inner().as_slice()) } } /// Returns a mutable slice of all elements that have not been yielded yet. /// /// Equivalent to [`array::IntoIter::as_mut_slice`]. - pub fn as_mut_slice(&mut self) -> &mut UniqueEntitySlice { + pub fn as_mut_slice(&mut self) -> &mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.as_mut_inner().as_mut_slice()) } + unsafe { + UniqueEntityEquivalentSlice::from_slice_unchecked_mut( + self.as_mut_inner().as_mut_slice(), + ) + } } } diff --git a/crates/bevy_ecs/src/entity/unique_slice.rs b/crates/bevy_ecs/src/entity/unique_slice.rs index df2f1dc6d9..e45c3a21c0 100644 --- a/crates/bevy_ecs/src/entity/unique_slice.rs +++ b/crates/bevy_ecs/src/entity/unique_slice.rs @@ -22,40 +22,46 @@ use alloc::{ vec::Vec, }; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use super::{ - unique_array::UniqueEntityArray, - unique_vec::{self, UniqueEntityVec}, - Entity, EntitySet, EntitySetIterator, FromEntitySetIterator, TrustedEntityBorrow, - UniqueEntityIter, + unique_vec::{self, UniqueEntityEquivalentVec}, + Entity, EntityEquivalent, EntitySet, EntitySetIterator, FromEntitySetIterator, + UniqueEntityEquivalentArray, UniqueEntityIter, }; /// A slice that contains only unique entities. /// -/// It can be obtained by slicing [`UniqueEntityVec`]. +/// This can be obtained by slicing [`UniqueEntityEquivalentVec`]. +/// +/// When `T` is [`Entity`], use [`UniqueEntitySlice`]. #[repr(transparent)] #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct UniqueEntitySlice([T]); +pub struct UniqueEntityEquivalentSlice([T]); -impl UniqueEntitySlice { - /// Constructs a `UniqueEntitySlice` from a [`&[T]`] unsafely. +/// A slice that contains only unique [`Entity`]. +/// +/// This is the default case of a [`UniqueEntityEquivalentSlice`]. +pub type UniqueEntitySlice = UniqueEntityEquivalentSlice; + +impl UniqueEntityEquivalentSlice { + /// Constructs a `UniqueEntityEquivalentSlice` from a [`&[T]`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub const unsafe fn from_slice_unchecked(slice: &[T]) -> &Self { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { &*(ptr::from_ref(slice) as *const Self) } } - /// Constructs a `UniqueEntitySlice` from a [`&mut [T]`] unsafely. + /// Constructs a `UniqueEntityEquivalentSlice` from a [`&mut [T]`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub const unsafe fn from_slice_unchecked_mut(slice: &mut [T]) -> &mut Self { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { &mut *(ptr::from_mut(slice) as *mut Self) } } @@ -64,51 +70,51 @@ impl UniqueEntitySlice { &self.0 } - /// Constructs a `UniqueEntitySlice` from a [`Box<[T]>`] unsafely. + /// Constructs a `UniqueEntityEquivalentSlice` from a [`Box<[T]>`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub unsafe fn from_boxed_slice_unchecked(slice: Box<[T]>) -> Box { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Box::from_raw(Box::into_raw(slice) as *mut Self) } } /// Casts `self` to the inner slice. pub fn into_boxed_inner(self: Box) -> Box<[T]> { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Box::from_raw(Box::into_raw(self) as *mut [T]) } } - /// Constructs a `UniqueEntitySlice` from a [`Arc<[T]>`] unsafely. + /// Constructs a `UniqueEntityEquivalentSlice` from a [`Arc<[T]>`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub unsafe fn from_arc_slice_unchecked(slice: Arc<[T]>) -> Arc { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Arc::from_raw(Arc::into_raw(slice) as *mut Self) } } /// Casts `self` to the inner slice. pub fn into_arc_inner(this: Arc) -> Arc<[T]> { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Arc::from_raw(Arc::into_raw(this) as *mut [T]) } } - // Constructs a `UniqueEntitySlice` from a [`Rc<[T]>`] unsafely. + // Constructs a `UniqueEntityEquivalentSlice` from a [`Rc<[T]>`] unsafely. /// /// # Safety /// /// `slice` must contain only unique elements. pub unsafe fn from_rc_slice_unchecked(slice: Rc<[T]>) -> Rc { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Rc::from_raw(Rc::into_raw(slice) as *mut Self) } } /// Casts `self` to the inner slice. pub fn into_rc_inner(self: Rc) -> Rc<[T]> { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. unsafe { Rc::from_raw(Rc::into_raw(self) as *mut [T]) } } @@ -137,12 +143,12 @@ impl UniqueEntitySlice { /// Returns an array reference to the first `N` items in the slice. /// /// Equivalent to [`[T]::first_chunk`](slice::first_chunk). - pub const fn first_chunk(&self) -> Option<&UniqueEntityArray> { + pub const fn first_chunk(&self) -> Option<&UniqueEntityEquivalentArray> { let Some(chunk) = self.0.first_chunk() else { return None; }; // SAFETY: All elements in the original slice are unique. - Some(unsafe { UniqueEntityArray::from_array_ref_unchecked(chunk) }) + Some(unsafe { UniqueEntityEquivalentArray::from_array_ref_unchecked(chunk) }) } /// Returns an array reference to the first `N` items in the slice and the remaining slice. @@ -150,14 +156,17 @@ impl UniqueEntitySlice { /// Equivalent to [`[T]::split_first_chunk`](slice::split_first_chunk). pub const fn split_first_chunk( &self, - ) -> Option<(&UniqueEntityArray, &UniqueEntitySlice)> { + ) -> Option<( + &UniqueEntityEquivalentArray, + &UniqueEntityEquivalentSlice, + )> { let Some((chunk, rest)) = self.0.split_first_chunk() else { return None; }; // SAFETY: All elements in the original slice are unique. unsafe { Some(( - UniqueEntityArray::from_array_ref_unchecked(chunk), + UniqueEntityEquivalentArray::from_array_ref_unchecked(chunk), Self::from_slice_unchecked(rest), )) } @@ -168,7 +177,10 @@ impl UniqueEntitySlice { /// Equivalent to [`[T]::split_last_chunk`](slice::split_last_chunk). pub const fn split_last_chunk( &self, - ) -> Option<(&UniqueEntitySlice, &UniqueEntityArray)> { + ) -> Option<( + &UniqueEntityEquivalentSlice, + &UniqueEntityEquivalentArray, + )> { let Some((rest, chunk)) = self.0.split_last_chunk() else { return None; }; @@ -176,7 +188,7 @@ impl UniqueEntitySlice { unsafe { Some(( Self::from_slice_unchecked(rest), - UniqueEntityArray::from_array_ref_unchecked(chunk), + UniqueEntityEquivalentArray::from_array_ref_unchecked(chunk), )) } } @@ -184,12 +196,12 @@ impl UniqueEntitySlice { /// Returns an array reference to the last `N` items in the slice. /// /// Equivalent to [`[T]::last_chunk`](slice::last_chunk). - pub const fn last_chunk(&self) -> Option<&UniqueEntityArray> { + pub const fn last_chunk(&self) -> Option<&UniqueEntityEquivalentArray> { let Some(chunk) = self.0.last_chunk() else { return None; }; // SAFETY: All elements in the original slice are unique. - Some(unsafe { UniqueEntityArray::from_array_ref_unchecked(chunk) }) + Some(unsafe { UniqueEntityEquivalentArray::from_array_ref_unchecked(chunk) }) } /// Returns a reference to a subslice. @@ -213,7 +225,7 @@ impl UniqueEntitySlice { /// /// Equivalent to the range functionality of [`[T]::get_mut`]. /// - /// Note that `UniqueEntitySlice::get_mut` cannot be called with a [`usize`]. + /// Note that `UniqueEntityEquivalentSlice::get_mut` cannot be called with a [`usize`]. /// /// [`[T]::get_mut`]: `slice::get_mut`s pub fn get_mut(&mut self, index: I) -> Option<&mut Self> @@ -249,7 +261,7 @@ impl UniqueEntitySlice { /// /// Equivalent to the range functionality of [`[T]::get_unchecked_mut`]. /// - /// Note that `UniqueEntitySlice::get_unchecked_mut` cannot be called with an index. + /// Note that `UniqueEntityEquivalentSlice::get_unchecked_mut` cannot be called with an index. /// /// # Safety /// @@ -299,7 +311,9 @@ impl UniqueEntitySlice { /// [`[T]::windows`]: `slice::windows` pub fn windows(&self, size: usize) -> Windows<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.windows(size)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.windows(size)) + } } /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the @@ -310,7 +324,11 @@ impl UniqueEntitySlice { /// [`[T]::chunks`]: `slice::chunks` pub fn chunks(&self, chunk_size: usize) -> Chunks<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.chunks(chunk_size)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked( + self.0.chunks(chunk_size), + ) + } } /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the @@ -322,7 +340,7 @@ impl UniqueEntitySlice { pub fn chunks_mut(&mut self, chunk_size: usize) -> ChunksMut<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( self.0.chunks_mut(chunk_size), ) } @@ -336,7 +354,9 @@ impl UniqueEntitySlice { pub fn chunks_exact(&self, chunk_size: usize) -> ChunksExact<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.chunks_exact(chunk_size)) + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked( + self.0.chunks_exact(chunk_size), + ) } } @@ -349,7 +369,7 @@ impl UniqueEntitySlice { pub fn chunks_exact_mut(&mut self, chunk_size: usize) -> ChunksExactMut<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( self.0.chunks_exact_mut(chunk_size), ) } @@ -363,7 +383,11 @@ impl UniqueEntitySlice { /// [`[T]::rchunks`]: `slice::rchunks` pub fn rchunks(&self, chunk_size: usize) -> RChunks<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.rchunks(chunk_size)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked( + self.0.rchunks(chunk_size), + ) + } } /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the end @@ -375,7 +399,7 @@ impl UniqueEntitySlice { pub fn rchunks_mut(&mut self, chunk_size: usize) -> RChunksMut<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( self.0.rchunks_mut(chunk_size), ) } @@ -390,7 +414,9 @@ impl UniqueEntitySlice { pub fn rchunks_exact(&self, chunk_size: usize) -> RChunksExact<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.rchunks_exact(chunk_size)) + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked( + self.0.rchunks_exact(chunk_size), + ) } } @@ -403,7 +429,7 @@ impl UniqueEntitySlice { pub fn rchunks_exact_mut(&mut self, chunk_size: usize) -> RChunksExactMut<'_, T> { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( self.0.rchunks_exact_mut(chunk_size), ) } @@ -420,7 +446,9 @@ impl UniqueEntitySlice { F: FnMut(&T, &T) -> bool, { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.chunk_by(pred)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.chunk_by(pred)) + } } /// Returns an iterator over the slice producing non-overlapping mutable @@ -435,7 +463,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.chunk_by_mut(pred)) + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( + self.0.chunk_by_mut(pred), + ) } } @@ -554,7 +584,9 @@ impl UniqueEntitySlice { F: FnMut(&T) -> bool, { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.split(pred)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.split(pred)) + } } /// Returns an iterator over mutable subslices separated by elements that @@ -569,7 +601,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.split_mut(pred)) + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( + self.0.split_mut(pred), + ) } } @@ -585,7 +619,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.split_inclusive(pred)) + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked( + self.0.split_inclusive(pred), + ) } } @@ -601,7 +637,7 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( self.0.split_inclusive_mut(pred), ) } @@ -618,7 +654,9 @@ impl UniqueEntitySlice { F: FnMut(&T) -> bool, { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.rsplit(pred)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.rsplit(pred)) + } } /// Returns an iterator over mutable subslices separated by elements that @@ -634,7 +672,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.rsplit_mut(pred)) + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( + self.0.rsplit_mut(pred), + ) } } @@ -649,7 +689,9 @@ impl UniqueEntitySlice { F: FnMut(&T) -> bool, { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.splitn(n, pred)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.splitn(n, pred)) + } } /// Returns an iterator over mutable subslices separated by elements that match @@ -664,7 +706,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.splitn_mut(n, pred)) + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( + self.0.splitn_mut(n, pred), + ) } } @@ -679,7 +723,9 @@ impl UniqueEntitySlice { F: FnMut(&T) -> bool, { // SAFETY: Any subslice of a unique slice is also unique. - unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.rsplitn(n, pred)) } + unsafe { + UniqueEntityEquivalentSliceIter::from_slice_iterator_unchecked(self.0.rsplitn(n, pred)) + } } /// Returns an iterator over subslices separated by elements that match @@ -694,7 +740,9 @@ impl UniqueEntitySlice { { // SAFETY: Any subslice of a unique slice is also unique. unsafe { - UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.rsplitn_mut(n, pred)) + UniqueEntityEquivalentSliceIterMut::from_mut_slice_iterator_unchecked( + self.0.rsplitn_mut(n, pred), + ) } } @@ -791,40 +839,40 @@ impl UniqueEntitySlice { self.0.sort_by_cached_key(f); } - /// Copies self into a new `UniqueEntityVec`. - pub fn to_vec(&self) -> UniqueEntityVec + /// Copies self into a new `UniqueEntityEquivalentVec`. + pub fn to_vec(&self) -> UniqueEntityEquivalentVec where T: Clone, { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntityVec::from_vec_unchecked(self.0.to_vec()) } + unsafe { UniqueEntityEquivalentVec::from_vec_unchecked(self.0.to_vec()) } } /// Converts `self` into a vector without clones or allocation. /// /// Equivalent to [`[T]::into_vec`](slice::into_vec). - pub fn into_vec(self: Box) -> UniqueEntityVec { + pub fn into_vec(self: Box) -> UniqueEntityEquivalentVec { // SAFETY: // This matches the implementation of `slice::into_vec`. // All elements in the original slice are unique. unsafe { let len = self.len(); let vec = Vec::from_raw_parts(Box::into_raw(self).cast::(), len, len); - UniqueEntityVec::from_vec_unchecked(vec) + UniqueEntityEquivalentVec::from_vec_unchecked(vec) } } } /// Converts a reference to T into a slice of length 1 (without copying). -pub const fn from_ref(s: &T) -> &UniqueEntitySlice { +pub const fn from_ref(s: &T) -> &UniqueEntityEquivalentSlice { // SAFETY: A slice with a length of 1 is always unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(slice::from_ref(s)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(slice::from_ref(s)) } } /// Converts a reference to T into a slice of length 1 (without copying). -pub const fn from_mut(s: &mut T) -> &mut UniqueEntitySlice { +pub const fn from_mut(s: &mut T) -> &mut UniqueEntityEquivalentSlice { // SAFETY: A slice with a length of 1 is always unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice::from_mut(s)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(slice::from_mut(s)) } } /// Forms a slice from a pointer and a length. @@ -835,12 +883,12 @@ pub const fn from_mut(s: &mut T) -> &mut UniqueEntitySli /// /// [`slice::from_raw_parts`] must be safe to call with `data` and `len`. /// Additionally, all elements in the resulting slice must be unique. -pub const unsafe fn from_raw_parts<'a, T: TrustedEntityBorrow>( +pub const unsafe fn from_raw_parts<'a, T: EntityEquivalent>( data: *const T, len: usize, -) -> &'a UniqueEntitySlice { +) -> &'a UniqueEntityEquivalentSlice { // SAFETY: The safety contract is upheld by the caller. - unsafe { UniqueEntitySlice::from_slice_unchecked(slice::from_raw_parts(data, len)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(slice::from_raw_parts(data, len)) } } /// Performs the same functionality as [`from_raw_parts`], except that a mutable slice is returned. @@ -851,51 +899,53 @@ pub const unsafe fn from_raw_parts<'a, T: TrustedEntityBorrow>( /// /// [`slice::from_raw_parts_mut`] must be safe to call with `data` and `len`. /// Additionally, all elements in the resulting slice must be unique. -pub const unsafe fn from_raw_parts_mut<'a, T: TrustedEntityBorrow>( +pub const unsafe fn from_raw_parts_mut<'a, T: EntityEquivalent>( data: *mut T, len: usize, -) -> &'a mut UniqueEntitySlice { +) -> &'a mut UniqueEntityEquivalentSlice { // SAFETY: The safety contract is upheld by the caller. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice::from_raw_parts_mut(data, len)) } + unsafe { + UniqueEntityEquivalentSlice::from_slice_unchecked_mut(slice::from_raw_parts_mut(data, len)) + } } -/// Casts a slice of entity slices to a slice of [`UniqueEntitySlice`]s. +/// Casts a slice of entity slices to a slice of [`UniqueEntityEquivalentSlice`]s. /// /// # Safety /// /// All elements in each of the casted slices must be unique. -pub unsafe fn cast_slice_of_unique_entity_slice<'a, 'b, T: TrustedEntityBorrow + 'a>( +pub unsafe fn cast_slice_of_unique_entity_slice<'a, 'b, T: EntityEquivalent + 'a>( slice: &'b [&'a [T]], -) -> &'b [&'a UniqueEntitySlice] { +) -> &'b [&'a UniqueEntityEquivalentSlice] { // SAFETY: All elements in the original iterator are unique slices. - unsafe { &*(ptr::from_ref(slice) as *const [&UniqueEntitySlice]) } + unsafe { &*(ptr::from_ref(slice) as *const [&UniqueEntityEquivalentSlice]) } } -/// Casts a mutable slice of entity slices to a slice of [`UniqueEntitySlice`]s. +/// Casts a mutable slice of entity slices to a slice of [`UniqueEntityEquivalentSlice`]s. /// /// # Safety /// /// All elements in each of the casted slices must be unique. -pub unsafe fn cast_slice_of_unique_entity_slice_mut<'a, 'b, T: TrustedEntityBorrow + 'a>( +pub unsafe fn cast_slice_of_unique_entity_slice_mut<'a, 'b, T: EntityEquivalent + 'a>( slice: &'b mut [&'a [T]], -) -> &'b mut [&'a UniqueEntitySlice] { +) -> &'b mut [&'a UniqueEntityEquivalentSlice] { // SAFETY: All elements in the original iterator are unique slices. - unsafe { &mut *(ptr::from_mut(slice) as *mut [&UniqueEntitySlice]) } + unsafe { &mut *(ptr::from_mut(slice) as *mut [&UniqueEntityEquivalentSlice]) } } -/// Casts a mutable slice of mutable entity slices to a slice of mutable [`UniqueEntitySlice`]s. +/// Casts a mutable slice of mutable entity slices to a slice of mutable [`UniqueEntityEquivalentSlice`]s. /// /// # Safety /// /// All elements in each of the casted slices must be unique. -pub unsafe fn cast_slice_of_mut_unique_entity_slice_mut<'a, 'b, T: TrustedEntityBorrow + 'a>( +pub unsafe fn cast_slice_of_mut_unique_entity_slice_mut<'a, 'b, T: EntityEquivalent + 'a>( slice: &'b mut [&'a mut [T]], -) -> &'b mut [&'a mut UniqueEntitySlice] { +) -> &'b mut [&'a mut UniqueEntityEquivalentSlice] { // SAFETY: All elements in the original iterator are unique slices. - unsafe { &mut *(ptr::from_mut(slice) as *mut [&mut UniqueEntitySlice]) } + unsafe { &mut *(ptr::from_mut(slice) as *mut [&mut UniqueEntityEquivalentSlice]) } } -impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a UniqueEntitySlice { +impl<'a, T: EntityEquivalent> IntoIterator for &'a UniqueEntityEquivalentSlice { type Item = &'a T; type IntoIter = Iter<'a, T>; @@ -905,7 +955,7 @@ impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a UniqueEntitySlice { } } -impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a Box> { +impl<'a, T: EntityEquivalent> IntoIterator for &'a Box> { type Item = &'a T; type IntoIter = Iter<'a, T>; @@ -915,7 +965,7 @@ impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a Box> } } -impl IntoIterator for Box> { +impl IntoIterator for Box> { type Item = T; type IntoIter = unique_vec::IntoIter; @@ -925,7 +975,7 @@ impl IntoIterator for Box> { } } -impl Deref for UniqueEntitySlice { +impl Deref for UniqueEntityEquivalentSlice { type Target = [T]; fn deref(&self) -> &Self::Target { @@ -933,99 +983,107 @@ impl Deref for UniqueEntitySlice { } } -impl AsRef<[T]> for UniqueEntitySlice { +impl AsRef<[T]> for UniqueEntityEquivalentSlice { fn as_ref(&self) -> &[T] { self } } -impl AsRef for UniqueEntitySlice { +impl AsRef for UniqueEntityEquivalentSlice { fn as_ref(&self) -> &Self { self } } -impl AsMut for UniqueEntitySlice { +impl AsMut for UniqueEntityEquivalentSlice { fn as_mut(&mut self) -> &mut Self { self } } -impl Borrow<[T]> for UniqueEntitySlice { +impl Borrow<[T]> for UniqueEntityEquivalentSlice { fn borrow(&self) -> &[T] { self } } -impl Clone for Box> { +impl Clone for Box> { fn clone(&self) -> Self { self.to_vec().into_boxed_slice() } } -impl Default for &UniqueEntitySlice { +impl Default for &UniqueEntityEquivalentSlice { fn default() -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(Default::default()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(Default::default()) } } } -impl Default for &mut UniqueEntitySlice { +impl Default for &mut UniqueEntityEquivalentSlice { fn default() -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(Default::default()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(Default::default()) } } } -impl Default for Box> { +impl Default for Box> { fn default() -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_boxed_slice_unchecked(Default::default()) } + unsafe { UniqueEntityEquivalentSlice::from_boxed_slice_unchecked(Default::default()) } } } -impl From<&UniqueEntitySlice> for Box> { - fn from(value: &UniqueEntitySlice) -> Self { - // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_boxed_slice_unchecked(value.0.into()) } - } -} - -impl From<&UniqueEntitySlice> for Arc> { - fn from(value: &UniqueEntitySlice) -> Self { - // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_arc_slice_unchecked(value.0.into()) } - } -} - -impl From<&UniqueEntitySlice> for Rc> { - fn from(value: &UniqueEntitySlice) -> Self { - // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_rc_slice_unchecked(value.0.into()) } - } -} - -impl<'a, T: TrustedEntityBorrow + Clone> From<&'a UniqueEntitySlice> - for Cow<'a, UniqueEntitySlice> +impl From<&UniqueEntityEquivalentSlice> + for Box> { - fn from(value: &'a UniqueEntitySlice) -> Self { + fn from(value: &UniqueEntityEquivalentSlice) -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityEquivalentSlice::from_boxed_slice_unchecked(value.0.into()) } + } +} + +impl From<&UniqueEntityEquivalentSlice> + for Arc> +{ + fn from(value: &UniqueEntityEquivalentSlice) -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityEquivalentSlice::from_arc_slice_unchecked(value.0.into()) } + } +} + +impl From<&UniqueEntityEquivalentSlice> + for Rc> +{ + fn from(value: &UniqueEntityEquivalentSlice) -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityEquivalentSlice::from_rc_slice_unchecked(value.0.into()) } + } +} + +impl<'a, T: EntityEquivalent + Clone> From<&'a UniqueEntityEquivalentSlice> + for Cow<'a, UniqueEntityEquivalentSlice> +{ + fn from(value: &'a UniqueEntityEquivalentSlice) -> Self { Cow::Borrowed(value) } } -impl From> - for Box> +impl From> + for Box> { - fn from(value: UniqueEntityArray) -> Self { + fn from(value: UniqueEntityEquivalentArray) -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_boxed_slice_unchecked(Box::new(value.into_inner())) } + unsafe { + UniqueEntityEquivalentSlice::from_boxed_slice_unchecked(Box::new(value.into_inner())) + } } } -impl<'a, T: TrustedEntityBorrow + Clone> From>> - for Box> +impl<'a, T: EntityEquivalent + Clone> From>> + for Box> { - fn from(value: Cow<'a, UniqueEntitySlice>) -> Self { + fn from(value: Cow<'a, UniqueEntityEquivalentSlice>) -> Self { match value { Cow::Borrowed(slice) => Box::from(slice), Cow::Owned(slice) => Box::from(slice), @@ -1033,264 +1091,274 @@ impl<'a, T: TrustedEntityBorrow + Clone> From>> } } -impl From> for Box> { - fn from(value: UniqueEntityVec) -> Self { +impl From> + for Box> +{ + fn from(value: UniqueEntityEquivalentVec) -> Self { value.into_boxed_slice() } } -impl FromIterator for Box> { +impl FromIterator for Box> { fn from_iter>(iter: I) -> Self { iter.into_iter() - .collect::>() + .collect::>() .into_boxed_slice() } } -impl FromEntitySetIterator for Box> { +impl FromEntitySetIterator for Box> { fn from_entity_set_iter>(iter: I) -> Self { iter.into_iter() - .collect_set::>() + .collect_set::>() .into_boxed_slice() } } -impl, U: TrustedEntityBorrow> PartialEq> - for &UniqueEntitySlice +impl, U: EntityEquivalent> + PartialEq> for &UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.0.eq(other.as_vec()) } } -impl, U: TrustedEntityBorrow> PartialEq> - for &mut UniqueEntitySlice +impl, U: EntityEquivalent> + PartialEq> for &mut UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.0.eq(other.as_vec()) } } -impl, U: TrustedEntityBorrow> PartialEq> - for UniqueEntitySlice +impl, U: EntityEquivalent> + PartialEq> for UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.0.eq(other.as_vec()) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&UniqueEntitySlice> - for [T; N] +impl, U: EntityEquivalent, const N: usize> + PartialEq<&UniqueEntityEquivalentSlice> for [T; N] { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl + Clone, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> +impl + Clone, U: EntityEquivalent> PartialEq<&UniqueEntityEquivalentSlice> for Cow<'_, [T]> { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.eq(&&other.0) } } -impl + Clone, U: TrustedEntityBorrow> - PartialEq<&UniqueEntitySlice> for Cow<'_, UniqueEntitySlice> +impl + Clone, U: EntityEquivalent> + PartialEq<&UniqueEntityEquivalentSlice> for Cow<'_, UniqueEntityEquivalentSlice> { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.0.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> for Vec { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { +impl, U: EntityEquivalent> PartialEq<&UniqueEntityEquivalentSlice> for Vec { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> for VecDeque { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { +impl, U: EntityEquivalent> PartialEq<&UniqueEntityEquivalentSlice> + for VecDeque +{ + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.eq(&&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&mut UniqueEntitySlice> - for [T; N] +impl, U: EntityEquivalent, const N: usize> + PartialEq<&mut UniqueEntityEquivalentSlice> for [T; N] { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl + Clone, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> +impl + Clone, U: EntityEquivalent> PartialEq<&mut UniqueEntityEquivalentSlice> for Cow<'_, [T]> { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.eq(&&**other) } } -impl + Clone, U: TrustedEntityBorrow> - PartialEq<&mut UniqueEntitySlice> for Cow<'_, UniqueEntitySlice> +impl + Clone, U: EntityEquivalent> + PartialEq<&mut UniqueEntityEquivalentSlice> for Cow<'_, UniqueEntityEquivalentSlice> { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.0.eq(&other.0) } } -impl + Clone, U: TrustedEntityBorrow> - PartialEq> for Cow<'_, UniqueEntitySlice> +impl + Clone, U: EntityEquivalent> + PartialEq> for Cow<'_, UniqueEntityEquivalentSlice> { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.0.eq(other.as_vec()) } } -impl, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> for Vec { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { +impl, U: EntityEquivalent> PartialEq<&mut UniqueEntityEquivalentSlice> + for Vec +{ + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> for VecDeque { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { +impl, U: EntityEquivalent> PartialEq<&mut UniqueEntityEquivalentSlice> + for VecDeque +{ + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.eq(&&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> - for [T] +impl, U: EntityEquivalent> + PartialEq> for [T] { - fn eq(&self, other: &UniqueEntitySlice) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> PartialEq> +impl, U: EntityEquivalent, const N: usize> PartialEq> for [T; N] { - fn eq(&self, other: &UniqueEntitySlice) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> - for Vec +impl, U: EntityEquivalent> + PartialEq> for Vec { - fn eq(&self, other: &UniqueEntitySlice) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentSlice) -> bool { self.eq(&other.0) } } -impl, U, const N: usize> PartialEq<[U; N]> - for &UniqueEntitySlice +impl, U, const N: usize> PartialEq<[U; N]> + for &UniqueEntityEquivalentSlice { fn eq(&self, other: &[U; N]) -> bool { self.0.eq(other) } } -impl, U, const N: usize> PartialEq<[U; N]> - for &mut UniqueEntitySlice +impl, U, const N: usize> PartialEq<[U; N]> + for &mut UniqueEntityEquivalentSlice { fn eq(&self, other: &[U; N]) -> bool { self.0.eq(other) } } -impl, U, const N: usize> PartialEq<[U; N]> - for UniqueEntitySlice +impl, U, const N: usize> PartialEq<[U; N]> + for UniqueEntityEquivalentSlice { fn eq(&self, other: &[U; N]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq> for &UniqueEntitySlice +impl, U: EntityEquivalent, const N: usize> + PartialEq> for &UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.0.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq> for &mut UniqueEntitySlice +impl, U: EntityEquivalent, const N: usize> + PartialEq> for &mut UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.0.eq(&other.0) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq> for UniqueEntitySlice +impl, U: EntityEquivalent, const N: usize> + PartialEq> for UniqueEntityEquivalentSlice { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.0.eq(&other.0) } } -impl, U> PartialEq> for &UniqueEntitySlice { +impl, U> PartialEq> for &UniqueEntityEquivalentSlice { fn eq(&self, other: &Vec) -> bool { self.0.eq(other) } } -impl, U> PartialEq> for &mut UniqueEntitySlice { +impl, U> PartialEq> + for &mut UniqueEntityEquivalentSlice +{ fn eq(&self, other: &Vec) -> bool { self.0.eq(other) } } -impl, U> PartialEq> for UniqueEntitySlice { +impl, U> PartialEq> for UniqueEntityEquivalentSlice { fn eq(&self, other: &Vec) -> bool { self.0.eq(other) } } -impl ToOwned for UniqueEntitySlice { - type Owned = UniqueEntityVec; +impl ToOwned for UniqueEntityEquivalentSlice { + type Owned = UniqueEntityEquivalentVec; fn to_owned(&self) -> Self::Owned { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntityVec::from_vec_unchecked(self.0.to_owned()) } + unsafe { UniqueEntityEquivalentVec::from_vec_unchecked(self.0.to_owned()) } } } -impl<'a, T: TrustedEntityBorrow + Copy, const N: usize> TryFrom<&'a UniqueEntitySlice> - for &'a UniqueEntityArray +impl<'a, T: EntityEquivalent + Copy, const N: usize> TryFrom<&'a UniqueEntityEquivalentSlice> + for &'a UniqueEntityEquivalentArray { type Error = TryFromSliceError; - fn try_from(value: &'a UniqueEntitySlice) -> Result { + fn try_from(value: &'a UniqueEntityEquivalentSlice) -> Result { <&[T; N]>::try_from(&value.0).map(|array| // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntityArray::from_array_ref_unchecked(array) }) + unsafe { UniqueEntityEquivalentArray::from_array_ref_unchecked(array) }) } } -impl TryFrom<&UniqueEntitySlice> - for UniqueEntityArray +impl TryFrom<&UniqueEntityEquivalentSlice> + for UniqueEntityEquivalentArray { type Error = TryFromSliceError; - fn try_from(value: &UniqueEntitySlice) -> Result { + fn try_from(value: &UniqueEntityEquivalentSlice) -> Result { <&Self>::try_from(value).copied() } } -impl TryFrom<&mut UniqueEntitySlice> - for UniqueEntityArray +impl TryFrom<&mut UniqueEntityEquivalentSlice> + for UniqueEntityEquivalentArray { type Error = TryFromSliceError; - fn try_from(value: &mut UniqueEntitySlice) -> Result { + fn try_from(value: &mut UniqueEntityEquivalentSlice) -> Result { ::try_from(&*value) } } -impl Index<(Bound, Bound)> for UniqueEntitySlice { +impl Index<(Bound, Bound)> for UniqueEntityEquivalentSlice { type Output = Self; fn index(&self, key: (Bound, Bound)) -> &Self { // SAFETY: All elements in the original slice are unique. @@ -1298,7 +1366,7 @@ impl Index<(Bound, Bound)> for UniqueEntit } } -impl Index> for UniqueEntitySlice { +impl Index> for UniqueEntityEquivalentSlice { type Output = Self; fn index(&self, key: Range) -> &Self { // SAFETY: All elements in the original slice are unique. @@ -1306,7 +1374,7 @@ impl Index> for UniqueEntitySlice { } } -impl Index> for UniqueEntitySlice { +impl Index> for UniqueEntityEquivalentSlice { type Output = Self; fn index(&self, key: RangeFrom) -> &Self { // SAFETY: All elements in the original slice are unique. @@ -1314,7 +1382,7 @@ impl Index> for UniqueEntitySlice { } } -impl Index for UniqueEntitySlice { +impl Index for UniqueEntityEquivalentSlice { type Output = Self; fn index(&self, key: RangeFull) -> &Self { // SAFETY: All elements in the original slice are unique. @@ -1322,31 +1390,31 @@ impl Index for UniqueEntitySlice { } } -impl Index> for UniqueEntitySlice { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentSlice { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeInclusive) -> &Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntitySlice { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentSlice { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeTo) -> &Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntitySlice { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentSlice { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeToInclusive) -> &Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked(self.0.index(key)) } } } -impl Index for UniqueEntitySlice { +impl Index for UniqueEntityEquivalentSlice { type Output = T; fn index(&self, index: usize) -> &T { @@ -1354,49 +1422,51 @@ impl Index for UniqueEntitySlice { } } -impl IndexMut<(Bound, Bound)> for UniqueEntitySlice { +impl IndexMut<(Bound, Bound)> + for UniqueEntityEquivalentSlice +{ fn index_mut(&mut self, key: (Bound, Bound)) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntitySlice { +impl IndexMut> for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: Range) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntitySlice { +impl IndexMut> for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: RangeFrom) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut for UniqueEntitySlice { +impl IndexMut for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: RangeFull) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntitySlice { +impl IndexMut> for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: RangeInclusive) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntitySlice { +impl IndexMut> for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: RangeTo) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntitySlice { +impl IndexMut> for UniqueEntityEquivalentSlice { fn index_mut(&mut self, key: RangeToInclusive) -> &mut Self { // SAFETY: All elements in the original slice are unique. unsafe { Self::from_slice_unchecked_mut(self.0.index_mut(key)) } @@ -1405,52 +1475,60 @@ impl IndexMut> for UniqueEntityS /// Immutable slice iterator. /// -/// This struct is created by [`iter`] method on [`UniqueEntitySlice`] and -/// the [`IntoIterator`] impls on it and [`UniqueEntityVec`]. +/// This struct is created by [`iter`] method on [`UniqueEntityEquivalentSlice`] and +/// the [`IntoIterator`] impls on it and [`UniqueEntityEquivalentVec`]. /// -/// [`iter`]: `UniqueEntitySlice::iter` +/// [`iter`]: `UniqueEntityEquivalentSlice::iter` pub type Iter<'a, T> = UniqueEntityIter>; -impl<'a, T: TrustedEntityBorrow> UniqueEntityIter> { +impl<'a, T: EntityEquivalent> UniqueEntityIter> { /// Views the underlying data as a subslice of the original data. /// /// Equivalent to [`slice::Iter::as_slice`]. - pub fn as_slice(&self) -> &'a UniqueEntitySlice { + pub fn as_slice(&self) -> &'a UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.as_inner().as_slice()) } } } /// Mutable slice iterator. pub type IterMut<'a, T> = UniqueEntityIter>; -impl<'a, T: TrustedEntityBorrow> UniqueEntityIter> { +impl<'a, T: EntityEquivalent> UniqueEntityIter> { /// Views the underlying data as a mutable subslice of the original data. /// /// Equivalent to [`slice::IterMut::into_slice`]. - pub fn into_slice(self) -> &'a mut UniqueEntitySlice { + pub fn into_slice(self) -> &'a mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.into_inner().into_slice()) } + unsafe { + UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.into_inner().into_slice()) + } } /// Views the underlying data as a subslice of the original data. /// /// Equivalent to [`slice::IterMut::as_slice`]. - pub fn as_slice(&self) -> &UniqueEntitySlice { + pub fn as_slice(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.as_inner().as_slice()) } } } -/// An iterator that yields `&UniqueEntitySlice`. Note that an entity may appear +/// An iterator that yields `&UniqueEntityEquivalentSlice`. Note that an entity may appear /// in multiple slices, depending on the wrapped iterator. #[derive(Debug)] -pub struct UniqueEntitySliceIter<'a, T: TrustedEntityBorrow + 'a, I: Iterator> { +pub struct UniqueEntityEquivalentSliceIter< + 'a, + T: EntityEquivalent + 'a, + I: Iterator, +> { pub(crate) iter: I, } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> UniqueEntitySliceIter<'a, T, I> { - /// Constructs a [`UniqueEntitySliceIter`] from a slice iterator unsafely. +impl<'a, T: EntityEquivalent + 'a, I: Iterator> + UniqueEntityEquivalentSliceIter<'a, T, I> +{ + /// Constructs a [`UniqueEntityEquivalentSliceIter`] from a slice iterator unsafely. /// /// # Safety /// @@ -1480,15 +1558,15 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> UniqueEntityS } } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator - for UniqueEntitySliceIter<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator> Iterator + for UniqueEntityEquivalentSliceIter<'a, T, I> { - type Item = &'a UniqueEntitySlice; + type Item = &'a UniqueEntityEquivalentSlice; fn next(&mut self) -> Option { self.iter.next().map(|slice| // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked(slice) }) + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(slice) }) } fn size_hint(&self) -> (usize, Option) { @@ -1496,30 +1574,30 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator } } -impl<'a, T: TrustedEntityBorrow + 'a, I: ExactSizeIterator> ExactSizeIterator - for UniqueEntitySliceIter<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: ExactSizeIterator> ExactSizeIterator + for UniqueEntityEquivalentSliceIter<'a, T, I> { } -impl<'a, T: TrustedEntityBorrow + 'a, I: DoubleEndedIterator> DoubleEndedIterator - for UniqueEntitySliceIter<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: DoubleEndedIterator> DoubleEndedIterator + for UniqueEntityEquivalentSliceIter<'a, T, I> { fn next_back(&mut self) -> Option { self.iter.next_back().map(|slice| // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked(slice) }) + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(slice) }) } } -impl<'a, T: TrustedEntityBorrow + 'a, I: FusedIterator> FusedIterator - for UniqueEntitySliceIter<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: FusedIterator> FusedIterator + for UniqueEntityEquivalentSliceIter<'a, T, I> { } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsRef<[&'a [T]]>> - AsRef<[&'a UniqueEntitySlice]> for UniqueEntitySliceIter<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator + AsRef<[&'a [T]]>> + AsRef<[&'a UniqueEntityEquivalentSlice]> for UniqueEntityEquivalentSliceIter<'a, T, I> { - fn as_ref(&self) -> &[&'a UniqueEntitySlice] { + fn as_ref(&self) -> &[&'a UniqueEntityEquivalentSlice] { // SAFETY: unsafe { cast_slice_of_unique_entity_slice(self.iter.as_ref()) } } @@ -1527,107 +1605,113 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsRef<[&'a [ /// An iterator over overlapping subslices of length `size`. /// -/// This struct is created by [`UniqueEntitySlice::windows`]. -pub type Windows<'a, T = Entity> = UniqueEntitySliceIter<'a, T, slice::Windows<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::windows`]. +pub type Windows<'a, T = Entity> = UniqueEntityEquivalentSliceIter<'a, T, slice::Windows<'a, T>>; /// An iterator over a slice in (non-overlapping) chunks (`chunk_size` elements at a /// time), starting at the beginning of the slice. /// -/// This struct is created by [`UniqueEntitySlice::chunks`]. -pub type Chunks<'a, T = Entity> = UniqueEntitySliceIter<'a, T, slice::Chunks<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::chunks`]. +pub type Chunks<'a, T = Entity> = UniqueEntityEquivalentSliceIter<'a, T, slice::Chunks<'a, T>>; /// An iterator over a slice in (non-overlapping) chunks (`chunk_size` elements at a /// time), starting at the beginning of the slice. /// -/// This struct is created by [`UniqueEntitySlice::chunks_exact`]. -pub type ChunksExact<'a, T = Entity> = UniqueEntitySliceIter<'a, T, slice::ChunksExact<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::chunks_exact`]. +pub type ChunksExact<'a, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::ChunksExact<'a, T>>; -impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIter<'a, T, slice::ChunksExact<'a, T>> { +impl<'a, T: EntityEquivalent> UniqueEntityEquivalentSliceIter<'a, T, slice::ChunksExact<'a, T>> { /// Returns the remainder of the original slice that is not going to be /// returned by the iterator. /// /// Equivalent to [`slice::ChunksExact::remainder`]. - pub fn remainder(&self) -> &'a UniqueEntitySlice { + pub fn remainder(&self) -> &'a UniqueEntityEquivalentSlice { // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.iter.remainder()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.iter.remainder()) } } } /// An iterator over a slice in (non-overlapping) chunks (`chunk_size` elements at a /// time), starting at the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rchunks`]. -pub type RChunks<'a, T = Entity> = UniqueEntitySliceIter<'a, T, slice::RChunks<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rchunks`]. +pub type RChunks<'a, T = Entity> = UniqueEntityEquivalentSliceIter<'a, T, slice::RChunks<'a, T>>; /// An iterator over a slice in (non-overlapping) chunks (`chunk_size` elements at a /// time), starting at the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rchunks_exact`]. -pub type RChunksExact<'a, T = Entity> = UniqueEntitySliceIter<'a, T, slice::RChunksExact<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rchunks_exact`]. +pub type RChunksExact<'a, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::RChunksExact<'a, T>>; -impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIter<'a, T, slice::RChunksExact<'a, T>> { +impl<'a, T: EntityEquivalent> UniqueEntityEquivalentSliceIter<'a, T, slice::RChunksExact<'a, T>> { /// Returns the remainder of the original slice that is not going to be /// returned by the iterator. /// /// Equivalent to [`slice::RChunksExact::remainder`]. - pub fn remainder(&self) -> &'a UniqueEntitySlice { + pub fn remainder(&self) -> &'a UniqueEntityEquivalentSlice { // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.iter.remainder()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.iter.remainder()) } } } /// An iterator over slice in (non-overlapping) chunks separated by a predicate. /// -/// This struct is created by [`UniqueEntitySlice::chunk_by`]. -pub type ChunkBy<'a, P, T = Entity> = UniqueEntitySliceIter<'a, T, slice::ChunkBy<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::chunk_by`]. +pub type ChunkBy<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::ChunkBy<'a, T, P>>; /// An iterator over subslices separated by elements that match a predicate /// function. /// -/// This struct is created by [`UniqueEntitySlice::split`]. -pub type Split<'a, P, T = Entity> = UniqueEntitySliceIter<'a, T, slice::Split<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::split`]. +pub type Split<'a, P, T = Entity> = UniqueEntityEquivalentSliceIter<'a, T, slice::Split<'a, T, P>>; /// An iterator over subslices separated by elements that match a predicate /// function. /// -/// This struct is created by [`UniqueEntitySlice::split_inclusive`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::split_inclusive`]. pub type SplitInclusive<'a, P, T = Entity> = - UniqueEntitySliceIter<'a, T, slice::SplitInclusive<'a, T, P>>; + UniqueEntityEquivalentSliceIter<'a, T, slice::SplitInclusive<'a, T, P>>; /// An iterator over subslices separated by elements that match a predicate /// function, starting from the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rsplit`]. -pub type RSplit<'a, P, T = Entity> = UniqueEntitySliceIter<'a, T, slice::RSplit<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rsplit`]. +pub type RSplit<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::RSplit<'a, T, P>>; /// An iterator over subslices separated by elements that match a predicate /// function, limited to a given number of splits. /// -/// This struct is created by [`UniqueEntitySlice::splitn`]. -pub type SplitN<'a, P, T = Entity> = UniqueEntitySliceIter<'a, T, slice::SplitN<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::splitn`]. +pub type SplitN<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::SplitN<'a, T, P>>; /// An iterator over subslices separated by elements that match a /// predicate function, limited to a given number of splits, starting /// from the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rsplitn`]. -pub type RSplitN<'a, P, T = Entity> = UniqueEntitySliceIter<'a, T, slice::RSplitN<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rsplitn`]. +pub type RSplitN<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIter<'a, T, slice::RSplitN<'a, T, P>>; -/// An iterator that yields `&mut UniqueEntitySlice`. Note that an entity may appear +/// An iterator that yields `&mut UniqueEntityEquivalentSlice`. Note that an entity may appear /// in multiple slices, depending on the wrapped iterator. #[derive(Debug)] -pub struct UniqueEntitySliceIterMut< +pub struct UniqueEntityEquivalentSliceIterMut< 'a, - T: TrustedEntityBorrow + 'a, + T: EntityEquivalent + 'a, I: Iterator, > { pub(crate) iter: I, } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> - UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator> + UniqueEntityEquivalentSliceIterMut<'a, T, I> { - /// Constructs a [`UniqueEntitySliceIterMut`] from a mutable slice iterator unsafely. + /// Constructs a [`UniqueEntityEquivalentSliceIterMut`] from a mutable slice iterator unsafely. /// /// # Safety /// @@ -1657,15 +1741,15 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> } } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator - for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator> Iterator + for UniqueEntityEquivalentSliceIterMut<'a, T, I> { - type Item = &'a mut UniqueEntitySlice; + type Item = &'a mut UniqueEntityEquivalentSlice; fn next(&mut self) -> Option { self.iter.next().map(|slice| // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice) }) + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(slice) }) } fn size_hint(&self) -> (usize, Option) { @@ -1673,39 +1757,40 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator } } -impl<'a, T: TrustedEntityBorrow + 'a, I: ExactSizeIterator> ExactSizeIterator - for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: ExactSizeIterator> ExactSizeIterator + for UniqueEntityEquivalentSliceIterMut<'a, T, I> { } -impl<'a, T: TrustedEntityBorrow + 'a, I: DoubleEndedIterator> - DoubleEndedIterator for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: DoubleEndedIterator> DoubleEndedIterator + for UniqueEntityEquivalentSliceIterMut<'a, T, I> { fn next_back(&mut self) -> Option { self.iter.next_back().map(|slice| // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice) }) + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(slice) }) } } -impl<'a, T: TrustedEntityBorrow + 'a, I: FusedIterator> FusedIterator - for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: FusedIterator> FusedIterator + for UniqueEntityEquivalentSliceIterMut<'a, T, I> { } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsRef<[&'a [T]]>> - AsRef<[&'a UniqueEntitySlice]> for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator + AsRef<[&'a [T]]>> + AsRef<[&'a UniqueEntityEquivalentSlice]> for UniqueEntityEquivalentSliceIterMut<'a, T, I> { - fn as_ref(&self) -> &[&'a UniqueEntitySlice] { + fn as_ref(&self) -> &[&'a UniqueEntityEquivalentSlice] { // SAFETY: All elements in the original iterator are unique slices. unsafe { cast_slice_of_unique_entity_slice(self.iter.as_ref()) } } } -impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsMut<[&'a mut [T]]>> - AsMut<[&'a mut UniqueEntitySlice]> for UniqueEntitySliceIterMut<'a, T, I> +impl<'a, T: EntityEquivalent + 'a, I: Iterator + AsMut<[&'a mut [T]]>> + AsMut<[&'a mut UniqueEntityEquivalentSlice]> + for UniqueEntityEquivalentSliceIterMut<'a, T, I> { - fn as_mut(&mut self) -> &mut [&'a mut UniqueEntitySlice] { + fn as_mut(&mut self) -> &mut [&'a mut UniqueEntityEquivalentSlice] { // SAFETY: All elements in the original iterator are unique slices. unsafe { cast_slice_of_mut_unique_entity_slice_mut(self.iter.as_mut()) } } @@ -1714,88 +1799,97 @@ impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsMut<[& /// An iterator over a slice in (non-overlapping) mutable chunks (`chunk_size` /// elements at a time), starting at the beginning of the slice. /// -/// This struct is created by [`UniqueEntitySlice::chunks_mut`]. -pub type ChunksMut<'a, T = Entity> = UniqueEntitySliceIterMut<'a, T, slice::ChunksMut<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::chunks_mut`]. +pub type ChunksMut<'a, T = Entity> = + UniqueEntityEquivalentSliceIterMut<'a, T, slice::ChunksMut<'a, T>>; /// An iterator over a slice in (non-overlapping) mutable chunks (`chunk_size` /// elements at a time), starting at the beginning of the slice. /// -/// This struct is created by [`UniqueEntitySlice::chunks_exact_mut`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::chunks_exact_mut`]. pub type ChunksExactMut<'a, T = Entity> = - UniqueEntitySliceIterMut<'a, T, slice::ChunksExactMut<'a, T>>; + UniqueEntityEquivalentSliceIterMut<'a, T, slice::ChunksExactMut<'a, T>>; -impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIterMut<'a, T, slice::ChunksExactMut<'a, T>> { +impl<'a, T: EntityEquivalent> + UniqueEntityEquivalentSliceIterMut<'a, T, slice::ChunksExactMut<'a, T>> +{ /// Returns the remainder of the original slice that is not going to be /// returned by the iterator. /// /// Equivalent to [`slice::ChunksExactMut::into_remainder`]. - pub fn into_remainder(self) -> &'a mut UniqueEntitySlice { + pub fn into_remainder(self) -> &'a mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.iter.into_remainder()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.iter.into_remainder()) } } } /// An iterator over a slice in (non-overlapping) mutable chunks (`chunk_size` /// elements at a time), starting at the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rchunks_mut`]. -pub type RChunksMut<'a, T = Entity> = UniqueEntitySliceIterMut<'a, T, slice::RChunksMut<'a, T>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rchunks_mut`]. +pub type RChunksMut<'a, T = Entity> = + UniqueEntityEquivalentSliceIterMut<'a, T, slice::RChunksMut<'a, T>>; /// An iterator over a slice in (non-overlapping) mutable chunks (`chunk_size` /// elements at a time), starting at the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rchunks_exact_mut`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::rchunks_exact_mut`]. pub type RChunksExactMut<'a, T = Entity> = - UniqueEntitySliceIterMut<'a, T, slice::RChunksExactMut<'a, T>>; + UniqueEntityEquivalentSliceIterMut<'a, T, slice::RChunksExactMut<'a, T>>; -impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIterMut<'a, T, slice::RChunksExactMut<'a, T>> { +impl<'a, T: EntityEquivalent> + UniqueEntityEquivalentSliceIterMut<'a, T, slice::RChunksExactMut<'a, T>> +{ /// Returns the remainder of the original slice that is not going to be /// returned by the iterator. /// /// Equivalent to [`slice::RChunksExactMut::into_remainder`]. - pub fn into_remainder(self) -> &'a mut UniqueEntitySlice { + pub fn into_remainder(self) -> &'a mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original iterator are unique slices. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.iter.into_remainder()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.iter.into_remainder()) } } } /// An iterator over slice in (non-overlapping) mutable chunks separated /// by a predicate. /// -/// This struct is created by [`UniqueEntitySlice::chunk_by_mut`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::chunk_by_mut`]. pub type ChunkByMut<'a, P, T = Entity> = - UniqueEntitySliceIterMut<'a, T, slice::ChunkByMut<'a, T, P>>; + UniqueEntityEquivalentSliceIterMut<'a, T, slice::ChunkByMut<'a, T, P>>; /// An iterator over the mutable subslices of the vector which are separated /// by elements that match `pred`. /// -/// This struct is created by [`UniqueEntitySlice::split_mut`]. -pub type SplitMut<'a, P, T = Entity> = UniqueEntitySliceIterMut<'a, T, slice::SplitMut<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::split_mut`]. +pub type SplitMut<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIterMut<'a, T, slice::SplitMut<'a, T, P>>; /// An iterator over the mutable subslices of the vector which are separated /// by elements that match `pred`. Unlike `SplitMut`, it contains the matched /// parts in the ends of the subslices. /// -/// This struct is created by [`UniqueEntitySlice::split_inclusive_mut`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::split_inclusive_mut`]. pub type SplitInclusiveMut<'a, P, T = Entity> = - UniqueEntitySliceIterMut<'a, T, slice::SplitInclusiveMut<'a, T, P>>; + UniqueEntityEquivalentSliceIterMut<'a, T, slice::SplitInclusiveMut<'a, T, P>>; /// An iterator over the subslices of the vector which are separated /// by elements that match `pred`, starting from the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rsplit_mut`]. -pub type RSplitMut<'a, P, T = Entity> = UniqueEntitySliceIterMut<'a, T, slice::RSplitMut<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::rsplit_mut`]. +pub type RSplitMut<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIterMut<'a, T, slice::RSplitMut<'a, T, P>>; /// An iterator over subslices separated by elements that match a predicate /// function, limited to a given number of splits. /// -/// This struct is created by [`UniqueEntitySlice::splitn_mut`]. -pub type SplitNMut<'a, P, T = Entity> = UniqueEntitySliceIterMut<'a, T, slice::SplitNMut<'a, T, P>>; +/// This struct is created by [`UniqueEntityEquivalentSlice::splitn_mut`]. +pub type SplitNMut<'a, P, T = Entity> = + UniqueEntityEquivalentSliceIterMut<'a, T, slice::SplitNMut<'a, T, P>>; /// An iterator over subslices separated by elements that match a /// predicate function, limited to a given number of splits, starting /// from the end of the slice. /// -/// This struct is created by [`UniqueEntitySlice::rsplitn_mut`]. +/// This struct is created by [`UniqueEntityEquivalentSlice::rsplitn_mut`]. pub type RSplitNMut<'a, P, T = Entity> = - UniqueEntitySliceIterMut<'a, T, slice::RSplitNMut<'a, T, P>>; + UniqueEntityEquivalentSliceIterMut<'a, T, slice::RSplitNMut<'a, T, P>>; diff --git a/crates/bevy_ecs/src/entity/unique_vec.rs b/crates/bevy_ecs/src/entity/unique_vec.rs index e22c4b0902..30f9984e70 100644 --- a/crates/bevy_ecs/src/entity/unique_vec.rs +++ b/crates/bevy_ecs/src/entity/unique_vec.rs @@ -17,12 +17,12 @@ use alloc::{ vec::{self, Vec}, }; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use super::{ - unique_array::UniqueEntityArray, - unique_slice::{self, UniqueEntitySlice}, - Entity, EntitySet, FromEntitySetIterator, TrustedEntityBorrow, UniqueEntityIter, + unique_slice::{self, UniqueEntityEquivalentSlice}, + Entity, EntityEquivalent, EntitySet, FromEntitySetIterator, UniqueEntityEquivalentArray, + UniqueEntityIter, }; /// A `Vec` that contains only unique entities. @@ -31,29 +31,36 @@ use super::{ /// This is always true when less than 2 entities are present. /// /// This type is best obtained by its `FromEntitySetIterator` impl, via either -/// `EntityIterator::collect_set` or `UniqueEntityVec::from_entity_iter`. +/// `EntityIterator::collect_set` or `UniqueEntityEquivalentVec::from_entity_iter`. /// /// While this type can be constructed via `Iterator::collect`, doing so is inefficient, /// and not recommended. +/// +/// When `T` is [`Entity`], use the [`UniqueEntityVec`] alias. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct UniqueEntityVec(Vec); +pub struct UniqueEntityEquivalentVec(Vec); -impl UniqueEntityVec { - /// Constructs a new, empty `UniqueEntityVec`. +/// A `Vec` that contains only unique [`Entity`]. +/// +/// This is the default case of a [`UniqueEntityEquivalentVec`]. +pub type UniqueEntityVec = UniqueEntityEquivalentVec; + +impl UniqueEntityEquivalentVec { + /// Constructs a new, empty `UniqueEntityEquivalentVec`. /// /// Equivalent to [`Vec::new`]. pub const fn new() -> Self { Self(Vec::new()) } - /// Constructs a new, empty `UniqueEntityVec` with at least the specified capacity. + /// Constructs a new, empty `UniqueEntityEquivalentVec` with at least the specified capacity. /// /// Equivalent to [`Vec::with_capacity`] pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } - /// Creates a `UniqueEntityVec` directly from a pointer, a length, and a capacity. + /// Creates a `UniqueEntityEquivalentVec` directly from a pointer, a length, and a capacity. /// /// Equivalent to [`Vec::from_raw_parts`]. /// @@ -66,7 +73,7 @@ impl UniqueEntityVec { Self(unsafe { Vec::from_raw_parts(ptr, length, capacity) }) } - /// Constructs a `UniqueEntityVec` from a [`Vec`] unsafely. + /// Constructs a `UniqueEntityEquivalentVec` from a [`Vec`] unsafely. /// /// # Safety /// @@ -112,7 +119,7 @@ impl UniqueEntityVec { } /// Reserves the minimum capacity for at least `additional` more elements to - /// be inserted in the given `UniqueEntityVec`. + /// be inserted in the given `UniqueEntityEquivalentVec`. /// /// Equivalent to [`Vec::reserve_exact`]. pub fn reserve_exact(&mut self, additional: usize) { @@ -149,19 +156,21 @@ impl UniqueEntityVec { self.0.shrink_to(min_capacity); } - /// Converts the vector into `Box>`. - pub fn into_boxed_slice(self) -> Box> { - // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. - unsafe { UniqueEntitySlice::from_boxed_slice_unchecked(self.0.into_boxed_slice()) } + /// Converts the vector into `Box>`. + pub fn into_boxed_slice(self) -> Box> { + // SAFETY: UniqueEntityEquivalentSlice is a transparent wrapper around [T]. + unsafe { + UniqueEntityEquivalentSlice::from_boxed_slice_unchecked(self.0.into_boxed_slice()) + } } /// Extracts a slice containing the entire vector. - pub fn as_slice(&self) -> &UniqueEntitySlice { + pub fn as_slice(&self) -> &UniqueEntityEquivalentSlice { self } /// Extracts a mutable slice of the entire vector. - pub fn as_mut_slice(&mut self) -> &mut UniqueEntitySlice { + pub fn as_mut_slice(&mut self) -> &mut UniqueEntityEquivalentSlice { self } @@ -301,7 +310,7 @@ impl UniqueEntityVec { /// # Safety /// /// `other` must contain no elements that equal any element in `self`. - pub unsafe fn append(&mut self, other: &mut UniqueEntityVec) { + pub unsafe fn append(&mut self, other: &mut UniqueEntityEquivalentVec) { self.0.append(&mut other.0); } @@ -368,10 +377,10 @@ impl UniqueEntityVec { self.0.resize_with(new_len, f); } - /// Consumes and leaks the Vec, returning a mutable reference to the contents, `&'a mut UniqueEntitySlice`. - pub fn leak<'a>(self) -> &'a mut UniqueEntitySlice { + /// Consumes and leaks the Vec, returning a mutable reference to the contents, `&'a mut UniqueEntityEquivalentSlice`. + pub fn leak<'a>(self) -> &'a mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.leak()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.leak()) } } /// Returns the remaining spare capacity of the vector as a slice of @@ -405,31 +414,31 @@ impl UniqueEntityVec { } } -impl Default for UniqueEntityVec { +impl Default for UniqueEntityEquivalentVec { fn default() -> Self { Self(Vec::default()) } } -impl Deref for UniqueEntityVec { - type Target = UniqueEntitySlice; +impl Deref for UniqueEntityEquivalentVec { + type Target = UniqueEntityEquivalentSlice; fn deref(&self) -> &Self::Target { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(&self.0) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(&self.0) } } } -impl DerefMut for UniqueEntityVec { +impl DerefMut for UniqueEntityEquivalentVec { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(&mut self.0) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) } } } -impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a UniqueEntityVec +impl<'a, T: EntityEquivalent> IntoIterator for &'a UniqueEntityEquivalentVec where - &'a T: TrustedEntityBorrow, + &'a T: EntityEquivalent, { type Item = &'a T; @@ -441,7 +450,7 @@ where } } -impl IntoIterator for UniqueEntityVec { +impl IntoIterator for UniqueEntityEquivalentVec { type Item = T; type IntoIter = IntoIter; @@ -452,402 +461,417 @@ impl IntoIterator for UniqueEntityVec { } } -impl AsMut for UniqueEntityVec { - fn as_mut(&mut self) -> &mut UniqueEntityVec { +impl AsMut for UniqueEntityEquivalentVec { + fn as_mut(&mut self) -> &mut UniqueEntityEquivalentVec { self } } -impl AsMut> for UniqueEntityVec { - fn as_mut(&mut self) -> &mut UniqueEntitySlice { +impl AsMut> for UniqueEntityEquivalentVec { + fn as_mut(&mut self) -> &mut UniqueEntityEquivalentSlice { self } } -impl AsRef for UniqueEntityVec { +impl AsRef for UniqueEntityEquivalentVec { fn as_ref(&self) -> &Self { self } } -impl AsRef> for UniqueEntityVec { +impl AsRef> for UniqueEntityEquivalentVec { fn as_ref(&self) -> &Vec { &self.0 } } -impl Borrow> for UniqueEntityVec { +impl Borrow> for UniqueEntityEquivalentVec { fn borrow(&self) -> &Vec { &self.0 } } -impl AsRef<[T]> for UniqueEntityVec { +impl AsRef<[T]> for UniqueEntityEquivalentVec { fn as_ref(&self) -> &[T] { &self.0 } } -impl AsRef> for UniqueEntityVec { - fn as_ref(&self) -> &UniqueEntitySlice { +impl AsRef> for UniqueEntityEquivalentVec { + fn as_ref(&self) -> &UniqueEntityEquivalentSlice { self } } -impl Borrow<[T]> for UniqueEntityVec { +impl Borrow<[T]> for UniqueEntityEquivalentVec { fn borrow(&self) -> &[T] { &self.0 } } -impl Borrow> for UniqueEntityVec { - fn borrow(&self) -> &UniqueEntitySlice { +impl Borrow> for UniqueEntityEquivalentVec { + fn borrow(&self) -> &UniqueEntityEquivalentSlice { self } } -impl BorrowMut> for UniqueEntityVec { - fn borrow_mut(&mut self) -> &mut UniqueEntitySlice { +impl BorrowMut> + for UniqueEntityEquivalentVec +{ + fn borrow_mut(&mut self) -> &mut UniqueEntityEquivalentSlice { self } } -impl, U> PartialEq> for UniqueEntityVec { +impl, U> PartialEq> for UniqueEntityEquivalentVec { fn eq(&self, other: &Vec) -> bool { self.0.eq(other) } } -impl, U> PartialEq<&[U]> for UniqueEntityVec { +impl, U> PartialEq<&[U]> for UniqueEntityEquivalentVec { fn eq(&self, other: &&[U]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> - for UniqueEntityVec +impl, U: EntityEquivalent> + PartialEq<&UniqueEntityEquivalentSlice> for UniqueEntityEquivalentVec { - fn eq(&self, other: &&UniqueEntitySlice) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentSlice) -> bool { self.0.eq(other) } } -impl, U> PartialEq<&mut [U]> for UniqueEntityVec { +impl, U> PartialEq<&mut [U]> for UniqueEntityEquivalentVec { fn eq(&self, other: &&mut [U]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow> - PartialEq<&mut UniqueEntitySlice> for UniqueEntityVec +impl, U: EntityEquivalent> + PartialEq<&mut UniqueEntityEquivalentSlice> for UniqueEntityEquivalentVec { - fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentSlice) -> bool { self.0.eq(other) } } -impl, U, const N: usize> PartialEq<&[U; N]> - for UniqueEntityVec +impl, U, const N: usize> PartialEq<&[U; N]> + for UniqueEntityEquivalentVec { fn eq(&self, other: &&[U; N]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq<&UniqueEntityArray> for UniqueEntityVec +impl, U: EntityEquivalent, const N: usize> + PartialEq<&UniqueEntityEquivalentArray> for UniqueEntityEquivalentVec { - fn eq(&self, other: &&UniqueEntityArray) -> bool { + fn eq(&self, other: &&UniqueEntityEquivalentArray) -> bool { self.0.eq(&other.as_inner()) } } -impl, U, const N: usize> PartialEq<&mut [U; N]> - for UniqueEntityVec +impl, U, const N: usize> PartialEq<&mut [U; N]> + for UniqueEntityEquivalentVec { fn eq(&self, other: &&mut [U; N]) -> bool { self.0.eq(&**other) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq<&mut UniqueEntityArray> for UniqueEntityVec +impl, U: EntityEquivalent, const N: usize> + PartialEq<&mut UniqueEntityEquivalentArray> for UniqueEntityEquivalentVec { - fn eq(&self, other: &&mut UniqueEntityArray) -> bool { + fn eq(&self, other: &&mut UniqueEntityEquivalentArray) -> bool { self.0.eq(other.as_inner()) } } -impl, U> PartialEq<[U]> for UniqueEntityVec { +impl, U> PartialEq<[U]> for UniqueEntityEquivalentVec { fn eq(&self, other: &[U]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow> PartialEq> - for UniqueEntityVec +impl, U: EntityEquivalent> + PartialEq> for UniqueEntityEquivalentVec { - fn eq(&self, other: &UniqueEntitySlice) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentSlice) -> bool { self.0.eq(&**other) } } -impl, U, const N: usize> PartialEq<[U; N]> - for UniqueEntityVec +impl, U, const N: usize> PartialEq<[U; N]> + for UniqueEntityEquivalentVec { fn eq(&self, other: &[U; N]) -> bool { self.0.eq(other) } } -impl, U: TrustedEntityBorrow, const N: usize> - PartialEq> for UniqueEntityVec +impl, U: EntityEquivalent, const N: usize> + PartialEq> for UniqueEntityEquivalentVec { - fn eq(&self, other: &UniqueEntityArray) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentArray) -> bool { self.0.eq(other.as_inner()) } } -impl, U: TrustedEntityBorrow> PartialEq> for Vec { - fn eq(&self, other: &UniqueEntityVec) -> bool { +impl, U: EntityEquivalent> PartialEq> for Vec { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> for &[T] { - fn eq(&self, other: &UniqueEntityVec) -> bool { +impl, U: EntityEquivalent> PartialEq> for &[T] { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> for &mut [T] { - fn eq(&self, other: &UniqueEntityVec) -> bool { +impl, U: EntityEquivalent> PartialEq> for &mut [T] { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> - for [T] +impl, U: EntityEquivalent> + PartialEq> for [T] { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl + Clone, U: TrustedEntityBorrow> PartialEq> +impl + Clone, U: EntityEquivalent> PartialEq> for Cow<'_, [T]> { - fn eq(&self, other: &UniqueEntityVec) -> bool { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl, U: TrustedEntityBorrow> PartialEq> for VecDeque { - fn eq(&self, other: &UniqueEntityVec) -> bool { +impl, U: EntityEquivalent> PartialEq> for VecDeque { + fn eq(&self, other: &UniqueEntityEquivalentVec) -> bool { self.eq(&other.0) } } -impl From<&UniqueEntitySlice> for UniqueEntityVec { - fn from(value: &UniqueEntitySlice) -> Self { +impl From<&UniqueEntityEquivalentSlice> + for UniqueEntityEquivalentVec +{ + fn from(value: &UniqueEntityEquivalentSlice) -> Self { value.to_vec() } } -impl From<&mut UniqueEntitySlice> for UniqueEntityVec { - fn from(value: &mut UniqueEntitySlice) -> Self { +impl From<&mut UniqueEntityEquivalentSlice> + for UniqueEntityEquivalentVec +{ + fn from(value: &mut UniqueEntityEquivalentSlice) -> Self { value.to_vec() } } -impl From>> for UniqueEntityVec { - fn from(value: Box>) -> Self { +impl From>> + for UniqueEntityEquivalentVec +{ + fn from(value: Box>) -> Self { value.into_vec() } } -impl From>> for UniqueEntityVec +impl From>> + for UniqueEntityEquivalentVec where - UniqueEntitySlice: ToOwned>, + UniqueEntityEquivalentSlice: ToOwned>, { - fn from(value: Cow>) -> Self { + fn from(value: Cow>) -> Self { value.into_owned() } } -impl From<&[T; 1]> for UniqueEntityVec { +impl From<&[T; 1]> for UniqueEntityEquivalentVec { fn from(value: &[T; 1]) -> Self { Self(Vec::from(value)) } } -impl From<&[T; 0]> for UniqueEntityVec { +impl From<&[T; 0]> for UniqueEntityEquivalentVec { fn from(value: &[T; 0]) -> Self { Self(Vec::from(value)) } } -impl From<&mut [T; 1]> for UniqueEntityVec { +impl From<&mut [T; 1]> for UniqueEntityEquivalentVec { fn from(value: &mut [T; 1]) -> Self { Self(Vec::from(value)) } } -impl From<&mut [T; 0]> for UniqueEntityVec { +impl From<&mut [T; 0]> for UniqueEntityEquivalentVec { fn from(value: &mut [T; 0]) -> Self { Self(Vec::from(value)) } } -impl From<[T; 1]> for UniqueEntityVec { +impl From<[T; 1]> for UniqueEntityEquivalentVec { fn from(value: [T; 1]) -> Self { Self(Vec::from(value)) } } -impl From<[T; 0]> for UniqueEntityVec { +impl From<[T; 0]> for UniqueEntityEquivalentVec { fn from(value: [T; 0]) -> Self { Self(Vec::from(value)) } } -impl From<&UniqueEntityArray> - for UniqueEntityVec +impl From<&UniqueEntityEquivalentArray> + for UniqueEntityEquivalentVec { - fn from(value: &UniqueEntityArray) -> Self { + fn from(value: &UniqueEntityEquivalentArray) -> Self { Self(Vec::from(value.as_inner().clone())) } } -impl From<&mut UniqueEntityArray> - for UniqueEntityVec +impl From<&mut UniqueEntityEquivalentArray> + for UniqueEntityEquivalentVec { - fn from(value: &mut UniqueEntityArray) -> Self { + fn from(value: &mut UniqueEntityEquivalentArray) -> Self { Self(Vec::from(value.as_inner().clone())) } } -impl From> for UniqueEntityVec { - fn from(value: UniqueEntityArray) -> Self { +impl From> + for UniqueEntityEquivalentVec +{ + fn from(value: UniqueEntityEquivalentArray) -> Self { Self(Vec::from(value.into_inner())) } } -impl From> for Vec { - fn from(value: UniqueEntityVec) -> Self { +impl From> for Vec { + fn from(value: UniqueEntityEquivalentVec) -> Self { value.0 } } -impl<'a, T: TrustedEntityBorrow + Clone> From> for Cow<'a, [T]> { - fn from(value: UniqueEntityVec) -> Self { +impl<'a, T: EntityEquivalent + Clone> From> for Cow<'a, [T]> { + fn from(value: UniqueEntityEquivalentVec) -> Self { Cow::from(value.0) } } -impl<'a, T: TrustedEntityBorrow + Clone> From> - for Cow<'a, UniqueEntitySlice> +impl<'a, T: EntityEquivalent + Clone> From> + for Cow<'a, UniqueEntityEquivalentSlice> { - fn from(value: UniqueEntityVec) -> Self { + fn from(value: UniqueEntityEquivalentVec) -> Self { Cow::Owned(value) } } -impl From> for Arc<[T]> { - fn from(value: UniqueEntityVec) -> Self { +impl From> for Arc<[T]> { + fn from(value: UniqueEntityEquivalentVec) -> Self { Arc::from(value.0) } } -impl From> for Arc> { - fn from(value: UniqueEntityVec) -> Self { +impl From> + for Arc> +{ + fn from(value: UniqueEntityEquivalentVec) -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_arc_slice_unchecked(Arc::from(value.0)) } + unsafe { UniqueEntityEquivalentSlice::from_arc_slice_unchecked(Arc::from(value.0)) } } } -impl From> for BinaryHeap { - fn from(value: UniqueEntityVec) -> Self { +impl From> for BinaryHeap { + fn from(value: UniqueEntityEquivalentVec) -> Self { BinaryHeap::from(value.0) } } -impl From> for Box<[T]> { - fn from(value: UniqueEntityVec) -> Self { +impl From> for Box<[T]> { + fn from(value: UniqueEntityEquivalentVec) -> Self { Box::from(value.0) } } -impl From> for Rc<[T]> { - fn from(value: UniqueEntityVec) -> Self { +impl From> for Rc<[T]> { + fn from(value: UniqueEntityEquivalentVec) -> Self { Rc::from(value.0) } } -impl From> for Rc> { - fn from(value: UniqueEntityVec) -> Self { +impl From> + for Rc> +{ + fn from(value: UniqueEntityEquivalentVec) -> Self { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_rc_slice_unchecked(Rc::from(value.0)) } + unsafe { UniqueEntityEquivalentSlice::from_rc_slice_unchecked(Rc::from(value.0)) } } } -impl From> for VecDeque { - fn from(value: UniqueEntityVec) -> Self { +impl From> for VecDeque { + fn from(value: UniqueEntityEquivalentVec) -> Self { VecDeque::from(value.0) } } -impl TryFrom> for Box<[T; N]> { - type Error = UniqueEntityVec; +impl TryFrom> for Box<[T; N]> { + type Error = UniqueEntityEquivalentVec; - fn try_from(value: UniqueEntityVec) -> Result { - Box::try_from(value.0).map_err(UniqueEntityVec) + fn try_from(value: UniqueEntityEquivalentVec) -> Result { + Box::try_from(value.0).map_err(UniqueEntityEquivalentVec) } } -impl TryFrom> - for Box> +impl TryFrom> + for Box> { - type Error = UniqueEntityVec; + type Error = UniqueEntityEquivalentVec; - fn try_from(value: UniqueEntityVec) -> Result { + fn try_from(value: UniqueEntityEquivalentVec) -> Result { Box::try_from(value.0) .map(|v| // SAFETY: All elements in the original Vec are unique. - unsafe { UniqueEntityArray::from_boxed_array_unchecked(v) }) - .map_err(UniqueEntityVec) + unsafe { UniqueEntityEquivalentArray::from_boxed_array_unchecked(v) }) + .map_err(UniqueEntityEquivalentVec) } } -impl TryFrom> for [T; N] { - type Error = UniqueEntityVec; +impl TryFrom> for [T; N] { + type Error = UniqueEntityEquivalentVec; - fn try_from(value: UniqueEntityVec) -> Result { - <[T; N] as TryFrom>>::try_from(value.0).map_err(UniqueEntityVec) + fn try_from(value: UniqueEntityEquivalentVec) -> Result { + <[T; N] as TryFrom>>::try_from(value.0).map_err(UniqueEntityEquivalentVec) } } -impl TryFrom> - for UniqueEntityArray +impl TryFrom> + for UniqueEntityEquivalentArray { - type Error = UniqueEntityVec; + type Error = UniqueEntityEquivalentVec; - fn try_from(value: UniqueEntityVec) -> Result { + fn try_from(value: UniqueEntityEquivalentVec) -> Result { <[T; N] as TryFrom>>::try_from(value.0) .map(|v| // SAFETY: All elements in the original Vec are unique. - unsafe { UniqueEntityArray::from_array_unchecked(v) }) - .map_err(UniqueEntityVec) + unsafe { UniqueEntityEquivalentArray::from_array_unchecked(v) }) + .map_err(UniqueEntityEquivalentVec) } } -impl From> for UniqueEntityVec { +impl From> for UniqueEntityEquivalentVec { fn from(value: BTreeSet) -> Self { Self(value.into_iter().collect::>()) } } -impl FromIterator for UniqueEntityVec { +impl FromIterator for UniqueEntityEquivalentVec { /// This impl only uses `Eq` to validate uniqueness, resulting in O(n^2) complexity. /// It can make sense for very low N, or if `T` implements neither `Ord` nor `Hash`. /// When possible, use `FromEntitySetIterator::from_entity_iter` instead. @@ -866,14 +890,14 @@ impl FromIterator for UniqueEntityVec { } } -impl FromEntitySetIterator for UniqueEntityVec { +impl FromEntitySetIterator for UniqueEntityEquivalentVec { fn from_entity_set_iter>(iter: I) -> Self { // SAFETY: `iter` is an `EntitySet`. unsafe { Self::from_vec_unchecked(Vec::from_iter(iter)) } } } -impl Extend for UniqueEntityVec { +impl Extend for UniqueEntityEquivalentVec { /// Use with caution, because this impl only uses `Eq` to validate uniqueness, /// resulting in O(n^2) complexity. /// It can make sense for very low N, or if `T` implements neither `Ord` nor `Hash`. @@ -900,7 +924,7 @@ impl Extend for UniqueEntityVec { } } -impl<'a, T: TrustedEntityBorrow + Copy + 'a> Extend<&'a T> for UniqueEntityVec { +impl<'a, T: EntityEquivalent + Copy + 'a> Extend<&'a T> for UniqueEntityEquivalentVec { /// Use with caution, because this impl only uses `Eq` to validate uniqueness, /// resulting in O(n^2) complexity. /// It can make sense for very low N, or if `T` implements neither `Ord` nor `Hash`. @@ -927,160 +951,164 @@ impl<'a, T: TrustedEntityBorrow + Copy + 'a> Extend<&'a T> for UniqueEntityVec Index<(Bound, Bound)> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index<(Bound, Bound)> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: (Bound, Bound)) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: Range) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeFrom) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeFull) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeInclusive) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeTo) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index> for UniqueEntityVec { - type Output = UniqueEntitySlice; +impl Index> for UniqueEntityEquivalentVec { + type Output = UniqueEntityEquivalentSlice; fn index(&self, key: RangeToInclusive) -> &Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.0.index(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.0.index(key)) } } } -impl Index for UniqueEntityVec { +impl Index for UniqueEntityEquivalentVec { type Output = T; fn index(&self, key: usize) -> &T { self.0.index(key) } } -impl IndexMut<(Bound, Bound)> for UniqueEntityVec { +impl IndexMut<(Bound, Bound)> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: (Bound, Bound)) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityVec { +impl IndexMut> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: Range) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityVec { +impl IndexMut> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: RangeFrom) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut for UniqueEntityVec { +impl IndexMut for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: RangeFull) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityVec { +impl IndexMut> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: RangeInclusive) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityVec { +impl IndexMut> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: RangeTo) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } -impl IndexMut> for UniqueEntityVec { +impl IndexMut> for UniqueEntityEquivalentVec { fn index_mut(&mut self, key: RangeToInclusive) -> &mut Self::Output { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.index_mut(key)) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(self.0.index_mut(key)) } } } /// An iterator that moves out of a vector. /// /// This `struct` is created by the [`IntoIterator::into_iter`] trait -/// method on [`UniqueEntityVec`]. +/// method on [`UniqueEntityEquivalentVec`]. pub type IntoIter = UniqueEntityIter>; -impl UniqueEntityIter> { +impl UniqueEntityIter> { /// Returns the remaining items of this iterator as a slice. /// /// Equivalent to [`vec::IntoIter::as_slice`]. - pub fn as_slice(&self) -> &UniqueEntitySlice { + pub fn as_slice(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.as_inner().as_slice()) } } /// Returns the remaining items of this iterator as a mutable slice. /// /// Equivalent to [`vec::IntoIter::as_mut_slice`]. - pub fn as_mut_slice(&mut self) -> &mut UniqueEntitySlice { + pub fn as_mut_slice(&mut self) -> &mut UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.as_mut_inner().as_mut_slice()) } + unsafe { + UniqueEntityEquivalentSlice::from_slice_unchecked_mut( + self.as_mut_inner().as_mut_slice(), + ) + } } } -/// A draining iterator for [`UniqueEntityVec`]. +/// A draining iterator for [`UniqueEntityEquivalentVec`]. /// -/// This struct is created by [`UniqueEntityVec::drain`]. +/// This struct is created by [`UniqueEntityEquivalentVec::drain`]. /// See its documentation for more. pub type Drain<'a, T = Entity> = UniqueEntityIter>; -impl<'a, T: TrustedEntityBorrow> UniqueEntityIter> { +impl<'a, T: EntityEquivalent> UniqueEntityIter> { /// Returns the remaining items of this iterator as a slice. /// /// Equivalent to [`vec::Drain::as_slice`]. - pub fn as_slice(&self) -> &UniqueEntitySlice { + pub fn as_slice(&self) -> &UniqueEntityEquivalentSlice { // SAFETY: All elements in the original slice are unique. - unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked(self.as_inner().as_slice()) } } } -/// A splicing iterator for [`UniqueEntityVec`]. +/// A splicing iterator for [`UniqueEntityEquivalentVec`]. /// -/// This struct is created by [`UniqueEntityVec::splice`]. +/// This struct is created by [`UniqueEntityEquivalentVec::splice`]. /// See its documentation for more. pub type Splice<'a, I> = UniqueEntityIter>; diff --git a/crates/bevy_ecs/src/entity/visit_entities.rs b/crates/bevy_ecs/src/entity/visit_entities.rs deleted file mode 100644 index 734c96e113..0000000000 --- a/crates/bevy_ecs/src/entity/visit_entities.rs +++ /dev/null @@ -1,149 +0,0 @@ -pub use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut}; - -use crate::entity::Entity; - -/// Apply an operation to all entities in a container. -/// -/// This is implemented by default for types that implement [`IntoIterator`]. -/// -/// It may be useful to implement directly for types that can't produce an -/// iterator for lifetime reasons, such as those involving internal mutexes. -pub trait VisitEntities { - /// Apply an operation to all contained entities. - fn visit_entities(&self, f: F); -} - -impl VisitEntities for T -where - for<'a> &'a T: IntoIterator, -{ - fn visit_entities(&self, f: F) { - self.into_iter().copied().for_each(f); - } -} - -impl VisitEntities for Entity { - fn visit_entities(&self, mut f: F) { - f(*self); - } -} - -/// Apply an operation to mutable references to all entities in a container. -/// -/// This is implemented by default for types that implement [`IntoIterator`]. -/// -/// It may be useful to implement directly for types that can't produce an -/// iterator for lifetime reasons, such as those involving internal mutexes. -pub trait VisitEntitiesMut: VisitEntities { - /// Apply an operation to mutable references to all contained entities. - fn visit_entities_mut(&mut self, f: F); -} - -impl VisitEntitiesMut for T -where - for<'a> &'a mut T: IntoIterator, -{ - fn visit_entities_mut(&mut self, f: F) { - self.into_iter().for_each(f); - } -} - -impl VisitEntitiesMut for Entity { - fn visit_entities_mut(&mut self, mut f: F) { - f(self); - } -} - -#[cfg(test)] -mod tests { - use crate::{ - entity::{hash_map::EntityHashMap, MapEntities, SceneEntityMapper}, - world::World, - }; - use alloc::{string::String, vec, vec::Vec}; - use bevy_platform_support::collections::HashSet; - - use super::*; - - #[derive(VisitEntities, Debug, PartialEq)] - struct Foo { - ordered: Vec, - unordered: HashSet, - single: Entity, - #[visit_entities(ignore)] - not_an_entity: String, - } - - // Need a manual impl since VisitEntitiesMut isn't implemented for `HashSet`. - // We don't expect users to actually do this - it's only for test purposes - // to prove out the automatic `MapEntities` impl we get with `VisitEntitiesMut`. - impl VisitEntitiesMut for Foo { - fn visit_entities_mut(&mut self, mut f: F) { - self.ordered.visit_entities_mut(&mut f); - self.unordered = self - .unordered - .drain() - .map(|mut entity| { - f(&mut entity); - entity - }) - .collect(); - f(&mut self.single); - } - } - - #[test] - fn visit_entities() { - let mut world = World::new(); - let entities = world.entities(); - let mut foo = Foo { - ordered: vec![entities.reserve_entity(), entities.reserve_entity()], - unordered: [ - entities.reserve_entity(), - entities.reserve_entity(), - entities.reserve_entity(), - ] - .into_iter() - .collect(), - single: entities.reserve_entity(), - not_an_entity: "Bar".into(), - }; - - let mut entity_map = EntityHashMap::::default(); - let mut remapped = Foo { - ordered: vec![], - unordered: HashSet::default(), - single: Entity::PLACEHOLDER, - not_an_entity: foo.not_an_entity.clone(), - }; - - // Note: this assumes that the VisitEntities derive is field-ordered, - // which isn't explicitly stated/guaranteed. - // If that changes, this test will fail, but that might be OK if - // we're intentionally breaking that assumption. - let mut i = 0; - foo.visit_entities(|entity| { - let new_entity = entities.reserve_entity(); - if i < foo.ordered.len() { - assert_eq!(entity, foo.ordered[i]); - remapped.ordered.push(new_entity); - } else if i < foo.ordered.len() + foo.unordered.len() { - assert!(foo.unordered.contains(&entity)); - remapped.unordered.insert(new_entity); - } else { - assert_eq!(entity, foo.single); - remapped.single = new_entity; - } - - entity_map.insert(entity, new_entity); - - i += 1; - }); - - SceneEntityMapper::world_scope(&mut entity_map, &mut world, |_, mapper| { - foo.map_entities(mapper); - }); - - assert_eq!(foo, remapped); - } -} diff --git a/crates/bevy_ecs/src/error/bevy_error.rs b/crates/bevy_ecs/src/error/bevy_error.rs index 4c5c2d89b4..c290e249b2 100644 --- a/crates/bevy_ecs/src/error/bevy_error.rs +++ b/crates/bevy_ecs/src/error/bevy_error.rs @@ -34,48 +34,11 @@ impl BevyError { pub fn downcast_ref(&self) -> Option<&E> { self.inner.error.downcast_ref::() } -} -/// This type exists (rather than having a `BevyError(Box, - #[cfg(feature = "backtrace")] - backtrace: std::backtrace::Backtrace, -} - -// NOTE: writing the impl this way gives us From<&str> ... nice! -impl From for BevyError -where - Box: From, -{ - #[cold] - fn from(error: E) -> Self { - BevyError { - inner: Box::new(InnerBevyError { - error: error.into(), - #[cfg(feature = "backtrace")] - backtrace: std::backtrace::Backtrace::capture(), - }), - } - } -} - -impl Display for BevyError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - writeln!(f, "{}", self.inner.error)?; - Ok(()) - } -} - -impl Debug for BevyError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - writeln!(f, "{:?}", self.inner.error)?; + fn format_backtrace(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { #[cfg(feature = "backtrace")] { + let f = _f; let backtrace = &self.inner.backtrace; if let std::backtrace::BacktraceStatus::Captured = backtrace.status() { let full_backtrace = std::env::var("BEVY_BACKTRACE").is_ok_and(|val| val == "full"); @@ -113,7 +76,7 @@ impl Debug for BevyError { break; } } - writeln!(f, "{}", line)?; + writeln!(f, "{line}")?; } if !full_backtrace { if std::thread::panicking() { @@ -123,7 +86,50 @@ impl Debug for BevyError { } } } + Ok(()) + } +} +/// This type exists (rather than having a `BevyError(Box, + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace, +} + +// NOTE: writing the impl this way gives us From<&str> ... nice! +impl From for BevyError +where + Box: From, +{ + #[cold] + fn from(error: E) -> Self { + BevyError { + inner: Box::new(InnerBevyError { + error: error.into(), + #[cfg(feature = "backtrace")] + backtrace: std::backtrace::Backtrace::capture(), + }), + } + } +} + +impl Display for BevyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "{}", self.inner.error)?; + self.format_backtrace(f)?; + Ok(()) + } +} + +impl Debug for BevyError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + writeln!(f, "{:?}", self.inner.error)?; + self.format_backtrace(f)?; Ok(()) } } diff --git a/crates/bevy_ecs/src/error/command_handling.rs b/crates/bevy_ecs/src/error/command_handling.rs new file mode 100644 index 0000000000..bf2741d376 --- /dev/null +++ b/crates/bevy_ecs/src/error/command_handling.rs @@ -0,0 +1,131 @@ +use core::{any::type_name, fmt}; + +use crate::{ + entity::Entity, + never::Never, + system::{entity_command::EntityCommandError, Command, EntityCommand}, + world::{error::EntityMutableFetchError, World}, +}; + +use super::{BevyError, ErrorContext, ErrorHandler}; + +/// Takes a [`Command`] that potentially returns a Result and uses a given error handler function to convert it into +/// a [`Command`] that internally handles an error if it occurs and returns `()`. +pub trait HandleError: Send + 'static { + /// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into + /// a [`Command`] that internally handles an error if it occurs and returns `()`. + fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command; + /// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into + /// a [`Command`] that internally handles an error if it occurs and returns `()`. + fn handle_error(self) -> impl Command; +} + +impl HandleError> for C +where + C: Command>, + E: Into, +{ + fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command { + move |world: &mut World| match self.apply(world) { + Ok(_) => {} + Err(err) => (error_handler)( + err.into(), + ErrorContext::Command { + name: type_name::().into(), + }, + ), + } + } + + fn handle_error(self) -> impl Command { + move |world: &mut World| match self.apply(world) { + Ok(_) => {} + Err(err) => world.default_error_handler()( + err.into(), + ErrorContext::Command { + name: type_name::().into(), + }, + ), + } + } +} + +impl HandleError for C +where + C: Command, +{ + fn handle_error_with(self, _error_handler: fn(BevyError, ErrorContext)) -> impl Command { + move |world: &mut World| { + self.apply(world); + } + } + + #[inline] + fn handle_error(self) -> impl Command { + move |world: &mut World| { + self.apply(world); + } + } +} + +impl HandleError for C +where + C: Command, +{ + #[inline] + fn handle_error_with(self, _error_handler: fn(BevyError, ErrorContext)) -> impl Command { + self + } + #[inline] + fn handle_error(self) -> impl Command { + self + } +} + +/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that +/// internally runs the [`EntityCommand`] on that entity. +/// +// NOTE: This is a separate trait from `EntityCommand` because "result-returning entity commands" and +// "non-result returning entity commands" require different implementations, so they cannot be automatically +// implemented. And this isn't the type of implementation that we want to thrust on people implementing +// EntityCommand. +pub trait CommandWithEntity { + /// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that + /// internally runs the [`EntityCommand`] on that entity. + fn with_entity(self, entity: Entity) -> impl Command + HandleError; +} + +impl CommandWithEntity> for C +where + C: EntityCommand, +{ + fn with_entity( + self, + entity: Entity, + ) -> impl Command> + + HandleError> { + move |world: &mut World| -> Result<(), EntityMutableFetchError> { + let entity = world.get_entity_mut(entity)?; + self.apply(entity); + Ok(()) + } + } +} + +impl CommandWithEntity>> for C +where + C: EntityCommand>, + Err: fmt::Debug + fmt::Display + Send + Sync + 'static, +{ + fn with_entity( + self, + entity: Entity, + ) -> impl Command>> + HandleError>> + { + move |world: &mut World| { + let entity = world.get_entity_mut(entity)?; + self.apply(entity) + .map_err(EntityCommandError::CommandFailed) + } + } +} diff --git a/crates/bevy_ecs/src/error/handler.rs b/crates/bevy_ecs/src/error/handler.rs index eb0d9809af..c89408b250 100644 --- a/crates/bevy_ecs/src/error/handler.rs +++ b/crates/bevy_ecs/src/error/handler.rs @@ -1,75 +1,154 @@ -use crate::{component::Tick, error::BevyError, resource::Resource}; +use core::fmt::Display; + +use crate::{component::Tick, error::BevyError, prelude::Resource}; use alloc::borrow::Cow; +use derive_more::derive::{Deref, DerefMut}; -/// Additional context for a failed system run. -pub struct SystemErrorContext { - /// The name of the system that failed. - pub name: Cow<'static, str>, - - /// The last tick that the system was run. - pub last_run: Tick, +/// Context for a [`BevyError`] to aid in debugging. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ErrorContext { + /// The error occurred in a system. + System { + /// The name of the system that failed. + name: Cow<'static, str>, + /// The last tick that the system was run. + last_run: Tick, + }, + /// The error occurred in a run condition. + RunCondition { + /// The name of the run condition that failed. + name: Cow<'static, str>, + /// The last tick that the run condition was evaluated. + last_run: Tick, + }, + /// The error occurred in a command. + Command { + /// The name of the command that failed. + name: Cow<'static, str>, + }, + /// The error occurred in an observer. + Observer { + /// The name of the observer that failed. + name: Cow<'static, str>, + /// The last tick that the observer was run. + last_run: Tick, + }, } -/// The default systems error handler stored as a resource in the [`World`](crate::world::World). -pub struct DefaultSystemErrorHandler(pub fn(BevyError, SystemErrorContext)); +impl Display for ErrorContext { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::System { name, .. } => { + write!(f, "System `{name}` failed") + } + Self::Command { name } => write!(f, "Command `{name}` failed"), + Self::Observer { name, .. } => { + write!(f, "Observer `{name}` failed") + } + Self::RunCondition { name, .. } => { + write!(f, "Run condition `{name}` failed") + } + } + } +} -impl Resource for DefaultSystemErrorHandler {} +impl ErrorContext { + /// The name of the ECS construct that failed. + pub fn name(&self) -> &str { + match self { + Self::System { name, .. } + | Self::Command { name, .. } + | Self::Observer { name, .. } + | Self::RunCondition { name, .. } => name, + } + } -impl Default for DefaultSystemErrorHandler { - fn default() -> Self { - Self(panic) + /// A string representation of the kind of ECS construct that failed. + /// + /// This is a simpler helper used for logging. + pub fn kind(&self) -> &str { + match self { + Self::System { .. } => "system", + Self::Command { .. } => "command", + Self::Observer { .. } => "observer", + Self::RunCondition { .. } => "run condition", + } } } macro_rules! inner { ($call:path, $e:ident, $c:ident) => { - $call!("Encountered an error in system `{}`: {:?}", $c.name, $e); + $call!( + "Encountered an error in {} `{}`: {}", + $c.kind(), + $c.name(), + $e + ); }; } +/// Defines how Bevy reacts to errors. +pub type ErrorHandler = fn(BevyError, ErrorContext); + +/// Error handler to call when an error is not handled otherwise. +/// Defaults to [`panic()`]. +/// +/// When updated while a [`Schedule`] is running, it doesn't take effect for +/// that schedule until it's completed. +/// +/// [`Schedule`]: crate::schedule::Schedule +#[derive(Resource, Deref, DerefMut, Copy, Clone)] +pub struct DefaultErrorHandler(pub ErrorHandler); + +impl Default for DefaultErrorHandler { + fn default() -> Self { + Self(panic) + } +} + /// Error handler that panics with the system error. #[track_caller] #[inline] -pub fn panic(error: BevyError, ctx: SystemErrorContext) { +pub fn panic(error: BevyError, ctx: ErrorContext) { inner!(panic, error, ctx); } /// Error handler that logs the system error at the `error` level. #[track_caller] #[inline] -pub fn error(error: BevyError, ctx: SystemErrorContext) { +pub fn error(error: BevyError, ctx: ErrorContext) { inner!(log::error, error, ctx); } /// Error handler that logs the system error at the `warn` level. #[track_caller] #[inline] -pub fn warn(error: BevyError, ctx: SystemErrorContext) { +pub fn warn(error: BevyError, ctx: ErrorContext) { inner!(log::warn, error, ctx); } /// Error handler that logs the system error at the `info` level. #[track_caller] #[inline] -pub fn info(error: BevyError, ctx: SystemErrorContext) { +pub fn info(error: BevyError, ctx: ErrorContext) { inner!(log::info, error, ctx); } /// Error handler that logs the system error at the `debug` level. #[track_caller] #[inline] -pub fn debug(error: BevyError, ctx: SystemErrorContext) { +pub fn debug(error: BevyError, ctx: ErrorContext) { inner!(log::debug, error, ctx); } /// Error handler that logs the system error at the `trace` level. #[track_caller] #[inline] -pub fn trace(error: BevyError, ctx: SystemErrorContext) { +pub fn trace(error: BevyError, ctx: ErrorContext) { inner!(log::trace, error, ctx); } /// Error handler that ignores the system error. #[track_caller] #[inline] -pub fn ignore(_: BevyError, _: SystemErrorContext) {} +pub fn ignore(_: BevyError, _: ErrorContext) {} diff --git a/crates/bevy_ecs/src/error/mod.rs b/crates/bevy_ecs/src/error/mod.rs index 4c8ad10d8e..231bdda940 100644 --- a/crates/bevy_ecs/src/error/mod.rs +++ b/crates/bevy_ecs/src/error/mod.rs @@ -1,18 +1,17 @@ -//! Error handling for "fallible" systems. +//! Error handling for Bevy systems, commands, and observers. //! //! When a system is added to a [`Schedule`], and its return type is that of [`Result`], then Bevy //! considers those systems to be "fallible", and the ECS scheduler will special-case the [`Err`] //! variant of the returned `Result`. //! -//! All [`BevyError`]s returned by a system are handled by an "error handler". By default, the +//! All [`BevyError`]s returned by a system, observer or command are handled by an "error handler". By default, the //! [`panic`] error handler function is used, resulting in a panic with the error message attached. //! -//! You can change the default behavior by registering a custom error handler, either globally or -//! per [`Schedule`]: -//! -//! - `App::set_system_error_handler` (via `bevy_app`) sets the global error handler for all systems of the -//! current [`World`] by modifying the [`DefaultSystemErrorHandler`]. -//! - [`Schedule::set_error_handler`] sets the error handler for all systems of that schedule. +//! You can change the default behavior by registering a custom error handler: +//! Use [`DefaultErrorHandler`] to set a custom error handler function for a world, +//! or `App::set_error_handler` for a whole app. +//! In practice, this is generally feature-flagged: panicking or loudly logging errors in development, +//! and quietly logging or ignoring them in production to avoid crashing the app. //! //! Bevy provides a number of pre-built error-handlers for you to use: //! @@ -29,51 +28,49 @@ //! signature: //! //! ```rust,ignore -//! fn(BevyError, SystemErrorContext) +//! fn(BevyError, ErrorContext) //! ``` //! -//! The [`SystemErrorContext`] allows you to access additional details relevant to providing -//! context surrounding the system error – such as the system's [`name`] – in your error messages. +//! The [`ErrorContext`] allows you to access additional details relevant to providing +//! context surrounding the error – such as the system's [`name`] – in your error messages. //! -//! For example: +//! ```rust, ignore +//! use bevy_ecs::error::{BevyError, ErrorContext, DefaultErrorHandler}; +//! use log::trace; //! -//! ```rust -//! # use bevy_ecs::prelude::*; -//! # use bevy_ecs::schedule::ScheduleLabel; -//! # use log::trace; -//! # fn update() -> Result { Ok(()) } -//! # #[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)] -//! # struct MySchedule; -//! # fn main() { -//! let mut schedule = Schedule::new(MySchedule); -//! schedule.add_systems(update); -//! schedule.set_error_handler(|error, ctx| { -//! if ctx.name.ends_with("update") { -//! trace!("Nothing to see here, move along."); -//! return; -//! } +//! fn my_error_handler(error: BevyError, ctx: ErrorContext) { +//! if ctx.name().ends_with("plz_ignore") { +//! trace!("Nothing to see here, move along."); +//! return; +//! } +//! bevy_ecs::error::error(error, ctx); +//! } //! -//! bevy_ecs::error::error(error, ctx); -//! }); -//! # } +//! fn main() { +//! let mut world = World::new(); +//! world.insert_resource(DefaultErrorHandler(my_error_handler)); +//! // Use your world here +//! } //! ``` //! //! If you need special handling of individual fallible systems, you can use Bevy's [`system piping -//! feature`] to capture the `Result` output of the system and handle it accordingly. +//! feature`] to capture the [`Result`] output of the system and handle it accordingly. +//! +//! When working with commands, you can handle the result of each command separately using the [`HandleError::handle_error_with`] method. //! //! [`Schedule`]: crate::schedule::Schedule //! [`panic`]: panic() //! [`World`]: crate::world::World -//! [`Schedule::set_error_handler`]: crate::schedule::Schedule::set_error_handler //! [`System`]: crate::system::System //! [`name`]: crate::system::System::name -//! [`App::set_system_error_handler`]: ../../bevy_app/struct.App.html#method.set_system_error_handler //! [`system piping feature`]: crate::system::In mod bevy_error; +mod command_handling; mod handler; pub use bevy_error::*; +pub use command_handling::*; pub use handler::*; /// A result type for use in fallible systems, commands and observers. diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index 90fccbf97a..d525ba2e57 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -93,7 +93,7 @@ pub trait Event: Send + Sync + 'static { /// /// This exists so we can easily get access to a unique [`ComponentId`] for each [`Event`] type, /// without requiring that [`Event`] types implement [`Component`] directly. -/// [`ComponentId`] is used internally as a unique identitifier for events because they are: +/// [`ComponentId`] is used internally as a unique identifier for events because they are: /// /// - Unique to each event type. /// - Can be quickly generated and looked up. diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index 9c19dc1689..3bb422b7bb 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -24,8 +24,13 @@ pub use mut_iterators::{EventMutIterator, EventMutIteratorWithId}; pub use mutator::EventMutator; pub use reader::EventReader; pub use registry::{EventRegistry, ShouldUpdateEvents}; +#[expect( + deprecated, + reason = "`EventUpdates` was renamed to `EventUpdateSystems`." +)] pub use update::{ - event_update_condition, event_update_system, signal_event_update_system, EventUpdates, + event_update_condition, event_update_system, signal_event_update_system, EventUpdateSystems, + EventUpdates, }; pub use writer::EventWriter; diff --git a/crates/bevy_ecs/src/event/mutator.rs b/crates/bevy_ecs/src/event/mutator.rs index 0afbeaa00d..e95037af5b 100644 --- a/crates/bevy_ecs/src/event/mutator.rs +++ b/crates/bevy_ecs/src/event/mutator.rs @@ -44,6 +44,7 @@ use bevy_ecs::{ #[derive(SystemParam, Debug)] pub struct EventMutator<'w, 's, E: Event> { pub(super) reader: Local<'s, EventCursor>, + #[system_param(validation_message = "Event not initialized")] events: ResMut<'w, Events>, } diff --git a/crates/bevy_ecs/src/event/reader.rs b/crates/bevy_ecs/src/event/reader.rs index bc0f4f86bc..995e2ca9e9 100644 --- a/crates/bevy_ecs/src/event/reader.rs +++ b/crates/bevy_ecs/src/event/reader.rs @@ -16,6 +16,7 @@ use bevy_ecs::{ #[derive(SystemParam, Debug)] pub struct EventReader<'w, 's, E: Event> { pub(super) reader: Local<'s, EventCursor>, + #[system_param(validation_message = "Event not initialized")] events: Res<'w, Events>, } diff --git a/crates/bevy_ecs/src/event/registry.rs b/crates/bevy_ecs/src/event/registry.rs index 231f792f68..0beb41cd25 100644 --- a/crates/bevy_ecs/src/event/registry.rs +++ b/crates/bevy_ecs/src/event/registry.rs @@ -81,7 +81,7 @@ impl EventRegistry { } } - /// Removes an event from the world and it's associated [`EventRegistry`]. + /// Removes an event from the world and its associated [`EventRegistry`]. pub fn deregister_events(world: &mut World) { let component_id = world.init_resource::>(); let mut registry = world.get_resource_or_init::(); diff --git a/crates/bevy_ecs/src/event/update.rs b/crates/bevy_ecs/src/event/update.rs index c7b43aef00..bdde1af0db 100644 --- a/crates/bevy_ecs/src/event/update.rs +++ b/crates/bevy_ecs/src/event/update.rs @@ -13,7 +13,11 @@ use super::registry::ShouldUpdateEvents; #[doc(hidden)] #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EventUpdates; +pub struct EventUpdateSystems; + +/// Deprecated alias for [`EventUpdateSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `EventUpdateSystems`.")] +pub type EventUpdates = EventUpdateSystems; /// Signals the [`event_update_system`] to run after `FixedUpdate` systems. /// diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index 91dd13898c..5854ab34fb 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -60,6 +60,7 @@ use bevy_ecs::{ /// [`Observer`]: crate::observer::Observer #[derive(SystemParam)] pub struct EventWriter<'w, E: Event> { + #[system_param(validation_message = "Event not initialized")] events: ResMut<'w, Events>, } @@ -97,38 +98,4 @@ impl<'w, E: Event> EventWriter<'w, E> { { self.events.send_default() } - - /// Sends an `event`, which can later be read by [`EventReader`](super::EventReader)s. - /// This method returns the [ID](`EventId`) of the sent `event`. - /// - /// See [`Events`] for details. - #[deprecated(since = "0.16.0", note = "Use `EventWriter::write` instead.")] - #[track_caller] - pub fn send(&mut self, event: E) -> EventId { - self.write(event) - } - - /// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::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. - #[deprecated(since = "0.16.0", note = "Use `EventWriter::write_batch` instead.")] - #[track_caller] - pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { - self.write_batch(events) - } - - /// Sends the default value of the event. Useful when the event is an empty struct. - /// This method returns the [ID](`EventId`) of the sent `event`. - /// - /// See [`Events`] for details. - #[deprecated(since = "0.16.0", note = "Use `EventWriter::write_default` instead.")] - #[track_caller] - pub fn send_default(&mut self) -> EventId - where - E: Default, - { - self.write_default() - } } diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 7f1d0c4180..c4e36dc4fc 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -19,6 +19,8 @@ use crate::{ use alloc::{format, string::String, vec::Vec}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::std_traits::ReflectDefault; +#[cfg(all(feature = "serialize", feature = "bevy_reflect"))] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use core::ops::Deref; use core::slice; use disqualified::ShortName; @@ -26,7 +28,7 @@ use log::warn; /// Stores the parent entity of this child entity with this component. /// -/// This is a [`Relationship`](crate::relationship::Relationship) component, and creates the canonical +/// This is a [`Relationship`] component, and creates the canonical /// "parent / child" hierarchy. This is the "source of truth" component, and it pairs with /// the [`Children`] [`RelationshipTarget`](crate::relationship::RelationshipTarget). /// @@ -54,9 +56,9 @@ use log::warn; /// # use bevy_ecs::prelude::*; /// # let mut world = World::new(); /// let root = world.spawn_empty().id(); -/// let child1 = world.spawn(ChildOf { parent: root }).id(); -/// let child2 = world.spawn(ChildOf { parent: root }).id(); -/// let grandchild = world.spawn(ChildOf { parent: child1 }).id(); +/// let child1 = world.spawn(ChildOf(root)).id(); +/// let child2 = world.spawn(ChildOf(root)).id(); +/// let grandchild = world.spawn(ChildOf(child1)).id(); /// /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); @@ -88,17 +90,29 @@ use log::warn; /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); /// ``` +/// +/// [`Relationship`]: crate::relationship::Relationship #[derive(Component, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( feature = "bevy_reflect", reflect(Component, PartialEq, Debug, FromWorld, Clone) )] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + all(feature = "serialize", feature = "bevy_reflect"), + reflect(Serialize, Deserialize) +)] #[relationship(relationship_target = Children)] #[doc(alias = "IsChild", alias = "Parent")] -pub struct ChildOf { +pub struct ChildOf(#[entities] pub Entity); + +impl ChildOf { /// The parent entity of this child entity. - pub parent: Entity, + #[inline] + pub fn parent(&self) -> Entity { + self.0 + } } // TODO: We need to impl either FromWorld or Default so ChildOf can be registered as Reflect. @@ -108,16 +122,14 @@ pub struct ChildOf { impl FromWorld for ChildOf { #[inline(always)] fn from_world(_world: &mut World) -> Self { - ChildOf { - parent: Entity::PLACEHOLDER, - } + ChildOf(Entity::PLACEHOLDER) } } /// Tracks which entities are children of this parent entity. /// /// A [`RelationshipTarget`] collection component that is populated -/// with entities that "target" this entity with the [`ChildOf`] [`Relationship`](crate::relationship::Relationship) component. +/// with entities that "target" this entity with the [`ChildOf`] [`Relationship`] component. /// /// Together, these components form the "canonical parent-child hierarchy". See the [`ChildOf`] component for the full /// description of this relationship and instructions on how to use it. @@ -131,6 +143,7 @@ impl FromWorld for ChildOf { /// using the [`IntoIterator`] trait. /// For more complex access patterns, see the [`RelationshipTarget`] trait. /// +/// [`Relationship`]: crate::relationship::Relationship /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget #[derive(Component, Default, Debug, PartialEq, Eq)] #[relationship_target(relationship = ChildOf, linked_spawn)] @@ -139,6 +152,96 @@ impl FromWorld for ChildOf { #[doc(alias = "IsParent")] pub struct Children(Vec); +impl Children { + /// Swaps the child at `a_index` with the child at `b_index`. + #[inline] + pub fn swap(&mut self, a_index: usize, b_index: usize) { + self.0.swap(a_index, b_index); + } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_by`]. + /// + /// For the unstable version, see [`sort_unstable_by`](Children::sort_unstable_by). + /// + /// See also [`sort_by_key`](Children::sort_by_key), [`sort_by_cached_key`](Children::sort_by_cached_key). + #[inline] + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.0.sort_by(compare); + } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_by_key`]. + /// + /// For the unstable version, see [`sort_unstable_by_key`](Children::sort_unstable_by_key). + /// + /// See also [`sort_by`](Children::sort_by), [`sort_by_cached_key`](Children::sort_by_cached_key). + #[inline] + pub fn sort_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_by_key(compare); + } + + /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. Only evaluates each key at most + /// once per sort, caching the intermediate results in memory. + /// + /// For the underlying implementation, see [`slice::sort_by_cached_key`]. + /// + /// See also [`sort_by`](Children::sort_by), [`sort_by_key`](Children::sort_by_key). + #[inline] + pub fn sort_by_cached_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_by_cached_key(compare); + } + + /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by`]. + /// + /// For the stable version, see [`sort_by`](Children::sort_by). + /// + /// See also [`sort_unstable_by_key`](Children::sort_unstable_by_key). + #[inline] + pub fn sort_unstable_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.0.sort_unstable_by(compare); + } + + /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. + /// + /// For the stable version, see [`sort_by_key`](Children::sort_by_key). + /// + /// See also [`sort_unstable_by`](Children::sort_unstable_by). + #[inline] + pub fn sort_unstable_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.0.sort_unstable_by_key(compare); + } +} + impl<'a> IntoIterator for &'a Children { type Item = ::Item; @@ -166,21 +269,69 @@ pub type ChildSpawnerCommands<'w> = RelatedSpawnerCommands<'w, ChildOf>; impl<'w> EntityWorldMut<'w> { /// Spawns children of this entity (with a [`ChildOf`] relationship) by taking a function that operates on a [`ChildSpawner`]. + /// See also [`with_related`](Self::with_related). pub fn with_children(&mut self, func: impl FnOnce(&mut ChildSpawner)) -> &mut Self { - self.with_related(func); + self.with_related_entities(func); self } /// Adds the given children to this entity + /// See also [`add_related`](Self::add_related). pub fn add_children(&mut self, children: &[Entity]) -> &mut Self { self.add_related::(children) } + /// Removes all the children from this entity. + /// See also [`clear_related`](Self::clear_related) + pub fn clear_children(&mut self) -> &mut Self { + self.clear_related::() + } + + /// Insert children at specific index. + /// See also [`insert_related`](Self::insert_related). + pub fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { + self.insert_related::(index, children) + } + /// Adds the given child to this entity + /// See also [`add_related`](Self::add_related). pub fn add_child(&mut self, child: Entity) -> &mut Self { self.add_related::(&[child]) } + /// Removes the relationship between this entity and the given entities. + pub fn remove_children(&mut self, children: &[Entity]) -> &mut Self { + self.remove_related::(children) + } + + /// Replaces all the related children with a new set of children. + pub fn replace_children(&mut self, children: &[Entity]) -> &mut Self { + self.replace_related::(children) + } + + /// Replaces all the related children with a new set of children. + /// + /// # Warning + /// + /// Failing to maintain the functions invariants may lead to erratic engine behavior including random crashes. + /// Refer to [`Self::replace_related_with_difference`] for a list of these invariants. + /// + /// # Panics + /// + /// Panics when debug assertions are enabled if an invariant is is broken and the command is executed. + pub fn replace_children_with_difference( + &mut self, + entities_to_unrelate: &[Entity], + entities_to_relate: &[Entity], + newly_related_entities: &[Entity], + ) -> &mut Self { + self.replace_related_with_difference::( + entities_to_unrelate, + entities_to_relate, + newly_related_entities, + ) + } + /// Spawns the passed bundle and adds it to this entity as a child. /// /// For efficient spawning of multiple children, use [`with_children`]. @@ -189,27 +340,10 @@ impl<'w> EntityWorldMut<'w> { pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { let parent = self.id(); self.world_scope(|world| { - world.spawn((bundle, ChildOf { parent })); + world.spawn((bundle, ChildOf(parent))); }); self } - - /// Removes the [`ChildOf`] component, if it exists. - #[deprecated(since = "0.16.0", note = "Use entity_mut.remove::()")] - pub fn remove_parent(&mut self) -> &mut Self { - self.remove::(); - self - } - - /// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists. - #[deprecated( - since = "0.16.0", - note = "Use entity_mut.insert(ChildOf { parent: entity })" - )] - pub fn set_parent(&mut self, parent: Entity) -> &mut Self { - self.insert(ChildOf { parent }); - self - } } impl<'a> EntityCommands<'a> { @@ -218,7 +352,7 @@ impl<'a> EntityCommands<'a> { &mut self, func: impl FnOnce(&mut RelatedSpawnerCommands), ) -> &mut Self { - self.with_related(func); + self.with_related_entities(func); self } @@ -227,36 +361,63 @@ impl<'a> EntityCommands<'a> { self.add_related::(children) } + /// Removes all the children from this entity. + /// See also [`clear_related`](Self::clear_related) + pub fn clear_children(&mut self) -> &mut Self { + self.clear_related::() + } + + /// Insert children at specific index. + /// See also [`insert_related`](Self::insert_related). + pub fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { + self.insert_related::(index, children) + } + /// Adds the given child to this entity pub fn add_child(&mut self, child: Entity) -> &mut Self { self.add_related::(&[child]) } + /// Removes the relationship between this entity and the given entities. + pub fn remove_children(&mut self, children: &[Entity]) -> &mut Self { + self.remove_related::(children) + } + + /// Replaces the children on this entity with a new list of children. + pub fn replace_children(&mut self, children: &[Entity]) -> &mut Self { + self.replace_related::(children) + } + + /// Replaces all the related entities with a new set of entities. + /// + /// # Warning + /// + /// Failing to maintain the functions invariants may lead to erratic engine behavior including random crashes. + /// Refer to [`EntityWorldMut::replace_related_with_difference`] for a list of these invariants. + /// + /// # Panics + /// + /// Panics when debug assertions are enabled if an invariant is is broken and the command is executed. + pub fn replace_children_with_difference( + &mut self, + entities_to_unrelate: &[Entity], + entities_to_relate: &[Entity], + newly_related_entities: &[Entity], + ) -> &mut Self { + self.replace_related_with_difference::( + entities_to_unrelate, + entities_to_relate, + newly_related_entities, + ) + } + /// Spawns the passed bundle and adds it to this entity as a child. /// /// For efficient spawning of multiple children, use [`with_children`]. /// /// [`with_children`]: EntityCommands::with_children pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { - let parent = self.id(); - self.commands.spawn((bundle, ChildOf { parent })); - self - } - - /// Removes the [`ChildOf`] component, if it exists. - #[deprecated(since = "0.16.0", note = "Use entity_commands.remove::()")] - pub fn remove_parent(&mut self) -> &mut Self { - self.remove::(); - self - } - - /// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists. - #[deprecated( - since = "0.16.0", - note = "Use entity_commands.insert(ChildOf { parent: entity })" - )] - pub fn set_parent(&mut self, parent: Entity) -> &mut Self { - self.insert(ChildOf { parent }); + self.with_related::(bundle); self } } @@ -272,7 +433,7 @@ pub fn validate_parent_has_component( return; }; if !world - .get_entity(child_of.parent) + .get_entity(child_of.parent()) .is_ok_and(|e| e.contains::()) { // TODO: print name here once Name lives in bevy_ecs @@ -283,7 +444,7 @@ pub fn validate_parent_has_component( caller.map(|c| format!("{c}: ")).unwrap_or_default(), ty_name = ShortName::of::(), name = name.map_or_else( - || format!("Entity {}", entity), + || format!("Entity {entity}"), |s| format!("The {s} entity") ), ); @@ -322,7 +483,7 @@ pub fn validate_parent_has_component( #[macro_export] macro_rules! children { [$($child:expr),*$(,)?] => { - $crate::hierarchy::Children::spawn(($($crate::spawn::Spawn($child)),*)) + $crate::hierarchy::Children::spawn($crate::recursive_spawn!($($child),*)) }; } @@ -372,9 +533,9 @@ mod tests { fn hierarchy() { let mut world = World::new(); let root = world.spawn_empty().id(); - let child1 = world.spawn(ChildOf { parent: root }).id(); - let grandchild = world.spawn(ChildOf { parent: child1 }).id(); - let child2 = world.spawn(ChildOf { parent: root }).id(); + let child1 = world.spawn(ChildOf(root)).id(); + let grandchild = world.spawn(ChildOf(child1)).id(); + let child2 = world.spawn(ChildOf(root)).id(); // Spawn let hierarchy = get_hierarchy(&world, root); @@ -395,7 +556,7 @@ mod tests { assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); // Insert - world.entity_mut(child1).insert(ChildOf { parent: root }); + world.entity_mut(child1).insert(ChildOf(root)); let hierarchy = get_hierarchy(&world, root); assert_eq!( hierarchy, @@ -450,11 +611,97 @@ mod tests { ); } + #[test] + fn insert_children() { + let mut world = World::new(); + let child1 = world.spawn_empty().id(); + let child2 = world.spawn_empty().id(); + let child3 = world.spawn_empty().id(); + let child4 = world.spawn_empty().id(); + + let mut entity_world_mut = world.spawn_empty(); + + let first_children = entity_world_mut.add_children(&[child1, child2]); + + let root = first_children.insert_children(1, &[child3, child4]).id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new(child1), + Node::new(child3), + Node::new(child4), + Node::new(child2) + ] + ) + ); + } + + // regression test for https://github.com/bevyengine/bevy/pull/19134 + #[test] + fn insert_children_index_bound() { + let mut world = World::new(); + let child1 = world.spawn_empty().id(); + let child2 = world.spawn_empty().id(); + let child3 = world.spawn_empty().id(); + let child4 = world.spawn_empty().id(); + + let mut entity_world_mut = world.spawn_empty(); + + let first_children = entity_world_mut.add_children(&[child1, child2]).id(); + let hierarchy = get_hierarchy(&world, first_children); + assert_eq!( + hierarchy, + Node::new_with(first_children, vec![Node::new(child1), Node::new(child2)]) + ); + + let root = world + .entity_mut(first_children) + .insert_children(usize::MAX, &[child3, child4]) + .id(); + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with( + root, + vec![ + Node::new(child1), + Node::new(child2), + Node::new(child3), + Node::new(child4), + ] + ) + ); + } + + #[test] + fn remove_children() { + let mut world = World::new(); + let child1 = world.spawn_empty().id(); + let child2 = world.spawn_empty().id(); + let child3 = world.spawn_empty().id(); + let child4 = world.spawn_empty().id(); + + let mut root = world.spawn_empty(); + root.add_children(&[child1, child2, child3, child4]); + root.remove_children(&[child2, child3]); + let root = root.id(); + + let hierarchy = get_hierarchy(&world, root); + assert_eq!( + hierarchy, + Node::new_with(root, vec![Node::new(child1), Node::new(child4)]) + ); + } + #[test] fn self_parenting_invalid() { let mut world = World::new(); let id = world.spawn_empty().id(); - world.entity_mut(id).insert(ChildOf { parent: id }); + world.entity_mut(id).insert(ChildOf(id)); assert!( world.entity(id).get::().is_none(), "invalid ChildOf relationships should self-remove" @@ -466,7 +713,7 @@ mod tests { let mut world = World::new(); let parent = world.spawn_empty().id(); world.entity_mut(parent).despawn(); - let id = world.spawn(ChildOf { parent }).id(); + let id = world.spawn(ChildOf(parent)).id(); assert!( world.entity(id).get::().is_none(), "invalid ChildOf relationships should self-remove" @@ -477,10 +724,10 @@ mod tests { fn reinsert_same_parent() { let mut world = World::new(); let parent = world.spawn_empty().id(); - let id = world.spawn(ChildOf { parent }).id(); - world.entity_mut(id).insert(ChildOf { parent }); + let id = world.spawn(ChildOf(parent)).id(); + world.entity_mut(id).insert(ChildOf(parent)); assert_eq!( - Some(&ChildOf { parent }), + Some(&ChildOf(parent)), world.entity(id).get::(), "ChildOf should still be there" ); @@ -493,16 +740,340 @@ mod tests { assert_eq!(world.entity(id).get::().unwrap().len(), 2,); } + #[test] + fn spawn_many_children() { + let mut world = World::new(); + + // 12 children should result in a flat tuple + let id = world + .spawn(children![(), (), (), (), (), (), (), (), (), (), (), ()]) + .id(); + + assert_eq!(world.entity(id).get::().unwrap().len(), 12,); + + // 13 will start nesting, but should nonetheless produce a flat hierarchy + let id = world + .spawn(children![ + (), + (), + (), + (), + (), + (), + (), + (), + (), + (), + (), + (), + (), + ]) + .id(); + + assert_eq!(world.entity(id).get::().unwrap().len(), 13,); + } + + #[test] + fn replace_children() { + let mut world = World::new(); + let parent = world.spawn(Children::spawn((Spawn(()), Spawn(())))).id(); + let &[child_a, child_b] = &world.entity(parent).get::().unwrap().0[..] else { + panic!("Tried to spawn 2 children on an entity and didn't get 2 children"); + }; + + let child_c = world.spawn_empty().id(); + + world + .entity_mut(parent) + .replace_children(&[child_a, child_c]); + + let children = world.entity(parent).get::().unwrap(); + + assert!(children.contains(&child_a)); + assert!(children.contains(&child_c)); + assert!(!children.contains(&child_b)); + + assert_eq!( + world.entity(child_a).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_c).get::().unwrap(), + &ChildOf(parent) + ); + assert!(world.entity(child_b).get::().is_none()); + } + + #[test] + fn replace_children_with_nothing() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + + world.entity_mut(parent).add_children(&[child_a, child_b]); + + assert_eq!(world.entity(parent).get::().unwrap().len(), 2); + + world.entity_mut(parent).replace_children(&[]); + + assert!(world.entity(child_a).get::().is_none()); + assert!(world.entity(child_b).get::().is_none()); + } + + #[test] + fn insert_same_child_twice() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child = world.spawn_empty().id(); + + world.entity_mut(parent).add_child(child); + world.entity_mut(parent).add_child(child); + + let children = world.get::(parent).unwrap(); + assert_eq!(children.0, [child]); + assert_eq!( + world.entity(child).get::().unwrap(), + &ChildOf(parent) + ); + } + + #[test] + fn replace_with_difference() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + let child_c = world.spawn_empty().id(); + let child_d = world.spawn_empty().id(); + + // Test inserting new relations + world.entity_mut(parent).replace_children_with_difference( + &[], + &[child_a, child_b], + &[child_a, child_b], + ); + + assert_eq!( + world.entity(child_a).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_b).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(parent).get::().unwrap().0, + [child_a, child_b] + ); + + // Test replacing relations and changing order + world.entity_mut(parent).replace_children_with_difference( + &[child_b], + &[child_d, child_c, child_a], + &[child_c, child_d], + ); + assert_eq!( + world.entity(child_a).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_c).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_d).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(parent).get::().unwrap().0, + [child_d, child_c, child_a] + ); + assert!(!world.entity(child_b).contains::()); + + // Test removing relationships + world.entity_mut(parent).replace_children_with_difference( + &[child_a, child_d, child_c], + &[], + &[], + ); + assert!(!world.entity(parent).contains::()); + assert!(!world.entity(child_a).contains::()); + assert!(!world.entity(child_b).contains::()); + assert!(!world.entity(child_c).contains::()); + assert!(!world.entity(child_d).contains::()); + } + + #[test] + fn replace_with_difference_on_empty() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + + world + .entity_mut(parent) + .replace_children_with_difference(&[child_a], &[], &[]); + + assert!(!world.entity(parent).contains::()); + assert!(!world.entity(child_a).contains::()); + } + + #[test] + fn replace_with_difference_totally_new_children() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + let child_c = world.spawn_empty().id(); + let child_d = world.spawn_empty().id(); + + // Test inserting new relations + world.entity_mut(parent).replace_children_with_difference( + &[], + &[child_a, child_b], + &[child_a, child_b], + ); + + assert_eq!( + world.entity(child_a).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_b).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(parent).get::().unwrap().0, + [child_a, child_b] + ); + + // Test replacing relations and changing order + world.entity_mut(parent).replace_children_with_difference( + &[child_b, child_a], + &[child_d, child_c], + &[child_c, child_d], + ); + assert_eq!( + world.entity(child_c).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(child_d).get::().unwrap(), + &ChildOf(parent) + ); + assert_eq!( + world.entity(parent).get::().unwrap().0, + [child_d, child_c] + ); + assert!(!world.entity(child_a).contains::()); + assert!(!world.entity(child_b).contains::()); + } + + #[test] + fn replace_children_order() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + let child_c = world.spawn_empty().id(); + let child_d = world.spawn_empty().id(); + + let initial_order = [child_a, child_b, child_c, child_d]; + world.entity_mut(parent).add_children(&initial_order); + + assert_eq!( + world.entity_mut(parent).get::().unwrap().0, + initial_order + ); + + let new_order = [child_d, child_b, child_a, child_c]; + world.entity_mut(parent).replace_children(&new_order); + + assert_eq!(world.entity(parent).get::().unwrap().0, new_order); + } + + #[test] + #[should_panic] + #[cfg_attr( + not(debug_assertions), + ignore = "we don't check invariants if debug assertions are off" + )] + fn replace_diff_invariant_overlapping_unrelate_with_relate() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + + world + .entity_mut(parent) + .replace_children_with_difference(&[], &[child_a], &[child_a]); + + // This should panic + world + .entity_mut(parent) + .replace_children_with_difference(&[child_a], &[child_a], &[]); + } + + #[test] + #[should_panic] + #[cfg_attr( + not(debug_assertions), + ignore = "we don't check invariants if debug assertions are off" + )] + fn replace_diff_invariant_overlapping_unrelate_with_newly() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + + world + .entity_mut(parent) + .replace_children_with_difference(&[], &[child_a], &[child_a]); + + // This should panic + world.entity_mut(parent).replace_children_with_difference( + &[child_b], + &[child_a, child_b], + &[child_b], + ); + } + + #[test] + #[should_panic] + #[cfg_attr( + not(debug_assertions), + ignore = "we don't check invariants if debug assertions are off" + )] + fn replace_diff_invariant_newly_not_subset() { + let mut world = World::new(); + + let parent = world.spawn_empty().id(); + let child_a = world.spawn_empty().id(); + let child_b = world.spawn_empty().id(); + + // This should panic + world.entity_mut(parent).replace_children_with_difference( + &[], + &[child_a, child_b], + &[child_a], + ); + } + #[test] fn child_replace_hook_skip() { let mut world = World::new(); let parent = world.spawn_empty().id(); let other = world.spawn_empty().id(); - let child = world.spawn(ChildOf { parent }).id(); - world.entity_mut(child).insert_with_relationship_hook_mode( - ChildOf { parent: other }, - RelationshipHookMode::Skip, - ); + let child = world.spawn(ChildOf(parent)).id(); + world + .entity_mut(child) + .insert_with_relationship_hook_mode(ChildOf(other), RelationshipHookMode::Skip); assert_eq!( &**world.entity(parent).get::().unwrap(), &[child], diff --git a/crates/bevy_ecs/src/identifier/error.rs b/crates/bevy_ecs/src/identifier/error.rs deleted file mode 100644 index a4679a12c6..0000000000 --- a/crates/bevy_ecs/src/identifier/error.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Error types for [`super::Identifier`] conversions. An ID can be converted -//! to various kinds, but these can fail if they are not valid forms of those -//! kinds. The error type in this module encapsulates the various failure modes. -use core::fmt; - -/// An Error type for [`super::Identifier`], mostly for providing error -/// handling for conversions of an ID to a type abstracting over the ID bits. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[non_exhaustive] -pub enum IdentifierError { - /// A given ID has an invalid value for initializing to a [`crate::identifier::Identifier`]. - InvalidIdentifier, - /// A given ID has an invalid configuration of bits for converting to an [`crate::entity::Entity`]. - InvalidEntityId(u64), -} - -impl fmt::Display for IdentifierError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InvalidIdentifier => write!( - f, - "The given id contains a zero value high component, which is invalid" - ), - Self::InvalidEntityId(_) => write!(f, "The given id is not a valid entity."), - } - } -} - -impl core::error::Error for IdentifierError {} diff --git a/crates/bevy_ecs/src/identifier/kinds.rs b/crates/bevy_ecs/src/identifier/kinds.rs deleted file mode 100644 index a5f57365fc..0000000000 --- a/crates/bevy_ecs/src/identifier/kinds.rs +++ /dev/null @@ -1,11 +0,0 @@ -/// The kinds of ID that [`super::Identifier`] can represent. Each -/// variant imposes different usages of the low/high segments -/// of the ID. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum IdKind { - /// An ID variant that is compatible with [`crate::entity::Entity`]. - Entity = 0, - /// A future ID variant. - Placeholder = 0b1000_0000, -} diff --git a/crates/bevy_ecs/src/identifier/masks.rs b/crates/bevy_ecs/src/identifier/masks.rs deleted file mode 100644 index 30ece9d8e3..0000000000 --- a/crates/bevy_ecs/src/identifier/masks.rs +++ /dev/null @@ -1,233 +0,0 @@ -use core::num::NonZero; - -use super::kinds::IdKind; - -/// Mask for extracting the value portion of a 32-bit high segment. This -/// yields 31-bits of total value, as the final bit (the most significant) -/// is reserved as a flag bit. Can be negated to extract the flag bit. -pub(crate) const HIGH_MASK: u32 = 0x7FFF_FFFF; - -/// Abstraction over masks needed to extract values/components of an [`super::Identifier`]. -pub(crate) struct IdentifierMask; - -impl IdentifierMask { - /// Returns the low component from a `u64` value - #[inline(always)] - pub(crate) const fn get_low(value: u64) -> u32 { - // This will truncate to the lowest 32 bits - value as u32 - } - - /// Returns the high component from a `u64` value - #[inline(always)] - pub(crate) const fn get_high(value: u64) -> u32 { - // This will discard the lowest 32 bits - (value >> u32::BITS) as u32 - } - - /// Pack a low and high `u32` values into a single `u64` value. - #[inline(always)] - pub(crate) const fn pack_into_u64(low: u32, high: u32) -> u64 { - ((high as u64) << u32::BITS) | (low as u64) - } - - /// Pack the [`IdKind`] bits into a high segment. - #[inline(always)] - pub(crate) const fn pack_kind_into_high(value: u32, kind: IdKind) -> u32 { - value | ((kind as u32) << 24) - } - - /// Extract the value component from a high segment of an [`super::Identifier`]. - #[inline(always)] - pub(crate) const fn extract_value_from_high(value: u32) -> u32 { - value & HIGH_MASK - } - - /// Extract the ID kind component from a high segment of an [`super::Identifier`]. - #[inline(always)] - pub(crate) const fn extract_kind_from_high(value: u32) -> IdKind { - // The negated HIGH_MASK will extract just the bit we need for kind. - let kind_mask = !HIGH_MASK; - let bit = value & kind_mask; - - if bit == kind_mask { - IdKind::Placeholder - } else { - IdKind::Entity - } - } - - /// Offsets a masked generation value by the specified amount, wrapping to 1 instead of 0. - /// Will never be greater than [`HIGH_MASK`] or less than `1`, and increments are masked to - /// never be greater than [`HIGH_MASK`]. - #[inline(always)] - pub(crate) const fn inc_masked_high_by(lhs: NonZero, rhs: u32) -> NonZero { - let lo = (lhs.get() & HIGH_MASK).wrapping_add(rhs & HIGH_MASK); - // Checks high 32 bit for whether we have overflowed 31 bits. - let overflowed = lo >> 31; - - // SAFETY: - // - Adding the overflow flag will offset overflows to start at 1 instead of 0 - // - The sum of `0x7FFF_FFFF` + `u32::MAX` + 1 (overflow) == `0x7FFF_FFFF` - // - If the operation doesn't overflow at 31 bits, no offsetting takes place - unsafe { NonZero::::new_unchecked(lo.wrapping_add(overflowed) & HIGH_MASK) } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_u64_parts() { - // Two distinct bit patterns per low/high component - let value: u64 = 0x7FFF_FFFF_0000_000C; - - assert_eq!(IdentifierMask::get_low(value), 0x0000_000C); - assert_eq!(IdentifierMask::get_high(value), 0x7FFF_FFFF); - } - - #[test] - fn extract_kind() { - // All bits are ones. - let high: u32 = 0xFFFF_FFFF; - - assert_eq!( - IdentifierMask::extract_kind_from_high(high), - IdKind::Placeholder - ); - - // Second and second to last bits are ones. - let high: u32 = 0x4000_0002; - - assert_eq!(IdentifierMask::extract_kind_from_high(high), IdKind::Entity); - } - - #[test] - fn extract_high_value() { - // All bits are ones. - let high: u32 = 0xFFFF_FFFF; - - // Excludes the most significant bit as that is a flag bit. - assert_eq!(IdentifierMask::extract_value_from_high(high), 0x7FFF_FFFF); - - // Start bit and end bit are ones. - let high: u32 = 0x8000_0001; - - assert_eq!(IdentifierMask::extract_value_from_high(high), 0x0000_0001); - - // Classic bit pattern. - let high: u32 = 0xDEAD_BEEF; - - assert_eq!(IdentifierMask::extract_value_from_high(high), 0x5EAD_BEEF); - } - - #[test] - fn pack_kind_bits() { - // All bits are ones expect the most significant bit, which is zero - let high: u32 = 0x7FFF_FFFF; - - assert_eq!( - IdentifierMask::pack_kind_into_high(high, IdKind::Placeholder), - 0xFFFF_FFFF - ); - - // Arbitrary bit pattern - let high: u32 = 0x00FF_FF00; - - assert_eq!( - IdentifierMask::pack_kind_into_high(high, IdKind::Entity), - // Remains unchanged as before - 0x00FF_FF00 - ); - - // Bit pattern that almost spells a word - let high: u32 = 0x40FF_EEEE; - - assert_eq!( - IdentifierMask::pack_kind_into_high(high, IdKind::Placeholder), - 0xC0FF_EEEE // Milk and no sugar, please. - ); - } - - #[test] - fn pack_into_u64() { - let high: u32 = 0x7FFF_FFFF; - let low: u32 = 0x0000_00CC; - - assert_eq!( - IdentifierMask::pack_into_u64(low, high), - 0x7FFF_FFFF_0000_00CC - ); - } - - #[test] - fn incrementing_masked_nonzero_high_is_safe() { - // Adding from lowest value with lowest to highest increment - // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK - assert_eq!( - NonZero::::MIN, - IdentifierMask::inc_masked_high_by(NonZero::::MIN, 0) - ); - assert_eq!( - NonZero::::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MIN, 1) - ); - assert_eq!( - NonZero::::new(3).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MIN, 2) - ); - assert_eq!( - NonZero::::MIN, - IdentifierMask::inc_masked_high_by(NonZero::::MIN, HIGH_MASK) - ); - assert_eq!( - NonZero::::MIN, - IdentifierMask::inc_masked_high_by(NonZero::::MIN, u32::MAX) - ); - // Adding from absolute highest value with lowest to highest increment - // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MAX, 0) - ); - assert_eq!( - NonZero::::MIN, - IdentifierMask::inc_masked_high_by(NonZero::::MAX, 1) - ); - assert_eq!( - NonZero::::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MAX, 2) - ); - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MAX, HIGH_MASK) - ); - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::MAX, u32::MAX) - ); - // Adding from actual highest value with lowest to highest increment - // No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 0) - ); - assert_eq!( - NonZero::::MIN, - IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 1) - ); - assert_eq!( - NonZero::::new(2).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), 2) - ); - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), HIGH_MASK) - ); - assert_eq!( - NonZero::::new(HIGH_MASK).unwrap(), - IdentifierMask::inc_masked_high_by(NonZero::::new(HIGH_MASK).unwrap(), u32::MAX) - ); - } -} diff --git a/crates/bevy_ecs/src/identifier/mod.rs b/crates/bevy_ecs/src/identifier/mod.rs deleted file mode 100644 index c08ea7b4aa..0000000000 --- a/crates/bevy_ecs/src/identifier/mod.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! A module for the unified [`Identifier`] ID struct, for use as a representation -//! of multiple types of IDs in a single, packed type. Allows for describing an [`crate::entity::Entity`], -//! or other IDs that can be packed and expressed within a `u64` sized type. -//! [`Identifier`]s cannot be created directly, only able to be converted from other -//! compatible IDs. -#[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; - -use self::{error::IdentifierError, kinds::IdKind, masks::IdentifierMask}; -use core::{hash::Hash, num::NonZero}; - -pub mod error; -pub(crate) mod kinds; -pub(crate) mod masks; - -/// A unified identifier for all entity and similar IDs. -/// -/// Has the same size as a `u64` integer, but the layout is split between a 32-bit low -/// segment, a 31-bit high segment, and the significant bit reserved as type flags to denote -/// entity kinds. -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy_reflect", reflect(opaque))] -#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Hash, PartialEq, Clone))] -// Alignment repr necessary to allow LLVM to better output -// optimized codegen for `to_bits`, `PartialEq` and `Ord`. -#[repr(C, align(8))] -pub struct Identifier { - // 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")] - low: u32, - high: NonZero, - #[cfg(target_endian = "big")] - low: u32, -} - -impl Identifier { - /// Construct a new [`Identifier`]. The `high` parameter is masked with the - /// `kind` so to pack the high value and bit flags into the same field. - #[inline(always)] - pub const fn new(low: u32, high: u32, kind: IdKind) -> Result { - // the high bits are masked to cut off the most significant bit - // as these are used for the type flags. This means that the high - // portion is only 31 bits, but this still provides 2^31 - // values/kinds/ids that can be stored in this segment. - let masked_value = IdentifierMask::extract_value_from_high(high); - - let packed_high = IdentifierMask::pack_kind_into_high(masked_value, kind); - - // If the packed high component ends up being zero, that means that we tried - // to initialize an Identifier into an invalid state. - if packed_high == 0 { - Err(IdentifierError::InvalidIdentifier) - } else { - // SAFETY: The high value has been checked to ensure it is never - // zero. - unsafe { - Ok(Self { - low, - high: NonZero::::new_unchecked(packed_high), - }) - } - } - } - - /// Returns the value of the low segment of the [`Identifier`]. - #[inline(always)] - pub const fn low(self) -> u32 { - self.low - } - - /// Returns the value of the high segment of the [`Identifier`]. This - /// does not apply any masking. - #[inline(always)] - pub const fn high(self) -> NonZero { - self.high - } - - /// Returns the masked value of the high segment of the [`Identifier`]. - /// Does not include the flag bits. - #[inline(always)] - pub const fn masked_high(self) -> u32 { - IdentifierMask::extract_value_from_high(self.high.get()) - } - - /// Returns the kind of [`Identifier`] from the high segment. - #[inline(always)] - pub const fn kind(self) -> IdKind { - IdentifierMask::extract_kind_from_high(self.high.get()) - } - - /// Convert the [`Identifier`] into a `u64`. - #[inline(always)] - pub const fn to_bits(self) -> u64 { - IdentifierMask::pack_into_u64(self.low, self.high.get()) - } - - /// Convert a `u64` into an [`Identifier`]. - /// - /// # Panics - /// - /// This method will likely panic if given `u64` values that did not come from [`Identifier::to_bits`]. - #[inline(always)] - pub const fn from_bits(value: u64) -> Self { - let id = Self::try_from_bits(value); - - match id { - Ok(id) => id, - Err(_) => panic!("Attempted to initialize invalid bits as an id"), - } - } - - /// Convert a `u64` into an [`Identifier`]. - /// - /// This method is the fallible counterpart to [`Identifier::from_bits`]. - #[inline(always)] - pub const fn try_from_bits(value: u64) -> Result { - let high = NonZero::::new(IdentifierMask::get_high(value)); - - match high { - Some(high) => Ok(Self { - low: IdentifierMask::get_low(value), - high, - }), - None => Err(IdentifierError::InvalidIdentifier), - } - } -} - -// By not short-circuiting in comparisons, we get better codegen. -// See -impl PartialEq for Identifier { - #[inline] - fn eq(&self, other: &Self) -> bool { - // By using `to_bits`, the codegen can be optimized out even - // further potentially. Relies on the correct alignment/field - // order of `Entity`. - self.to_bits() == other.to_bits() - } -} - -impl Eq for Identifier {} - -// The derive macro codegen output is not optimal and can't be optimized 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 optimize the codegen for Entity -// far beyond what the derive macro can. -// See -impl PartialOrd for Identifier { - #[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 optimized 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 optimize the codegen for Entity -// far beyond what the derive macro can. -// See -impl Ord for Identifier { - #[inline] - fn cmp(&self, other: &Self) -> core::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 Identifier { - #[inline] - fn hash(&self, state: &mut H) { - self.to_bits().hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn id_construction() { - let id = Identifier::new(12, 55, IdKind::Entity).unwrap(); - - assert_eq!(id.low(), 12); - assert_eq!(id.high().get(), 55); - assert_eq!( - IdentifierMask::extract_kind_from_high(id.high().get()), - IdKind::Entity - ); - } - - #[test] - fn from_bits() { - // This high value should correspond to the max high() value - // and also Entity flag. - let high = 0x7FFFFFFF; - let low = 0xC; - let bits: u64 = (high << u32::BITS) | low; - - let id = Identifier::try_from_bits(bits).unwrap(); - - assert_eq!(id.to_bits(), 0x7FFFFFFF0000000C); - assert_eq!(id.low(), low as u32); - assert_eq!(id.high().get(), 0x7FFFFFFF); - assert_eq!( - IdentifierMask::extract_kind_from_high(id.high().get()), - IdKind::Entity - ); - } - - #[rustfmt::skip] - #[test] - #[expect( - clippy::nonminimal_bool, - reason = "This intentionally tests all possible comparison operators as separate functions; thus, we don't want to rewrite these comparisons to use different operators." - )] - fn id_comparison() { - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() == Identifier::new(123, 456, IdKind::Entity).unwrap()); - assert!(Identifier::new(123, 456, IdKind::Placeholder).unwrap() == Identifier::new(123, 456, IdKind::Placeholder).unwrap()); - assert!(Identifier::new(123, 789, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Entity).unwrap()); - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 789, IdKind::Entity).unwrap()); - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(456, 123, IdKind::Entity).unwrap()); - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Placeholder).unwrap()); - - // ordering is by flag then high then by low - - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() >= Identifier::new(123, 456, IdKind::Entity).unwrap()); - assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() <= Identifier::new(123, 456, IdKind::Entity).unwrap()); - assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() < Identifier::new(123, 456, IdKind::Entity).unwrap())); - assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() > Identifier::new(123, 456, IdKind::Entity).unwrap())); - - assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(1, 9, IdKind::Entity).unwrap()); - assert!(Identifier::new(1, 9, IdKind::Entity).unwrap() > Identifier::new(9, 1, IdKind::Entity).unwrap()); - - assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(9, 1, IdKind::Placeholder).unwrap()); - assert!(Identifier::new(1, 9, IdKind::Placeholder).unwrap() > Identifier::new(1, 9, IdKind::Entity).unwrap()); - - assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() < Identifier::new(2, 1, IdKind::Entity).unwrap()); - assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() <= Identifier::new(2, 1, IdKind::Entity).unwrap()); - assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() > Identifier::new(1, 2, IdKind::Entity).unwrap()); - assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() >= Identifier::new(1, 2, IdKind::Entity).unwrap()); - } -} diff --git a/crates/bevy_ecs/src/intern.rs b/crates/bevy_ecs/src/intern.rs index 5639d5fbe3..b10e6a2ac6 100644 --- a/crates/bevy_ecs/src/intern.rs +++ b/crates/bevy_ecs/src/intern.rs @@ -5,7 +5,7 @@ //! and make comparisons for any type as fast as integers. use alloc::{borrow::ToOwned, boxed::Box}; -use bevy_platform_support::{ +use bevy_platform::{ collections::HashSet, hash::FixedHasher, sync::{PoisonError, RwLock}, @@ -170,7 +170,7 @@ impl Default for Interner { #[cfg(test)] mod tests { use alloc::{boxed::Box, string::ToString}; - use bevy_platform_support::hash::FixedHasher; + use bevy_platform::hash::FixedHasher; use core::hash::{BuildHasher, Hash, Hasher}; use crate::intern::{Internable, Interned, Interner}; diff --git a/crates/bevy_ecs/src/label.rs b/crates/bevy_ecs/src/label.rs index c404c563bd..9d3f6f838d 100644 --- a/crates/bevy_ecs/src/label.rs +++ b/crates/bevy_ecs/src/label.rs @@ -12,9 +12,6 @@ pub use alloc::boxed::Box; /// An object safe version of [`Eq`]. This trait is automatically implemented /// for any `'static` type that implements `Eq`. pub trait DynEq: Any { - /// Casts the type to `dyn Any`. - fn as_any(&self) -> &dyn Any; - /// This method tests for `self` and `other` values to be equal. /// /// Implementers should avoid returning `true` when the underlying types are @@ -29,12 +26,8 @@ impl DynEq for T where T: Any + Eq, { - fn as_any(&self) -> &dyn Any { - self - } - fn dyn_eq(&self, other: &dyn DynEq) -> bool { - if let Some(other) = other.as_any().downcast_ref::() { + if let Some(other) = (other as &dyn Any).downcast_ref::() { return self == other; } false @@ -44,9 +37,6 @@ where /// An object safe version of [`Hash`]. This trait is automatically implemented /// for any `'static` type that implements `Hash`. pub trait DynHash: DynEq { - /// Casts the type to `dyn Any`. - fn as_dyn_eq(&self) -> &dyn DynEq; - /// Feeds this value into the given [`Hasher`]. fn dyn_hash(&self, state: &mut dyn Hasher); } @@ -58,10 +48,6 @@ impl DynHash for T where T: DynEq + Hash, { - fn as_dyn_eq(&self) -> &dyn DynEq { - self - } - fn dyn_hash(&self, mut state: &mut dyn Hasher) { T::hash(self, &mut state); self.type_id().hash(&mut state); @@ -120,7 +106,7 @@ macro_rules! define_label { ) => { $(#[$label_attr])* - pub trait $label_trait_name: 'static + Send + Sync + ::core::fmt::Debug { + pub trait $label_trait_name: Send + Sync + ::core::fmt::Debug + $crate::label::DynEq + $crate::label::DynHash { $($trait_extra_methods)* @@ -129,12 +115,6 @@ macro_rules! define_label { ///`. fn dyn_clone(&self) -> $crate::label::Box; - /// Casts this value to a form where it can be compared with other type-erased values. - fn as_dyn_eq(&self) -> &dyn $crate::label::DynEq; - - /// Feeds this value into the given [`Hasher`]. - fn dyn_hash(&self, state: &mut dyn ::core::hash::Hasher); - /// Returns an [`Interned`] value corresponding to `self`. fn intern(&self) -> $crate::intern::Interned where Self: Sized { @@ -151,15 +131,6 @@ macro_rules! define_label { (**self).dyn_clone() } - /// Casts this value to a form where it can be compared with other type-erased values. - fn as_dyn_eq(&self) -> &dyn $crate::label::DynEq { - (**self).as_dyn_eq() - } - - fn dyn_hash(&self, state: &mut dyn ::core::hash::Hasher) { - (**self).dyn_hash(state); - } - fn intern(&self) -> Self { *self } @@ -167,7 +138,7 @@ macro_rules! define_label { impl PartialEq for dyn $label_trait_name { fn eq(&self, other: &Self) -> bool { - self.as_dyn_eq().dyn_eq(other.as_dyn_eq()) + self.dyn_eq(other) } } @@ -188,7 +159,7 @@ macro_rules! define_label { use ::core::ptr; // Test that both the type id and pointer address are equivalent. - self.as_dyn_eq().type_id() == other.as_dyn_eq().type_id() + self.type_id() == other.type_id() && ptr::addr_eq(ptr::from_ref::(self), ptr::from_ref::(other)) } @@ -196,7 +167,7 @@ macro_rules! define_label { use ::core::{hash::Hash, ptr}; // Hash the type id... - self.as_dyn_eq().type_id().hash(state); + self.type_id().hash(state); // ...and the pointer address. // Cast to a unit `()` first to discard any pointer metadata. diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 7ccfd4caa9..d8846b29a1 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -39,10 +39,10 @@ pub mod entity_disabling; pub mod error; pub mod event; pub mod hierarchy; -pub mod identifier; pub mod intern; pub mod label; pub mod name; +pub mod never; pub mod observer; pub mod query; #[cfg(feature = "bevy_reflect")] @@ -63,37 +63,33 @@ pub use bevy_ptr as ptr; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[expect( - deprecated, - reason = "`crate::schedule::apply_deferred` is considered deprecated; however, it may still be used by crates which consume `bevy_ecs`, so its removal here may cause confusion. It is intended to be removed in the Bevy 0.17 cycle." - )] #[doc(hidden)] pub use crate::{ bundle::Bundle, change_detection::{DetectChanges, DetectChangesMut, Mut, Ref}, children, component::Component, - entity::{Entity, EntityBorrow, EntityMapper}, + entity::{ContainsEntity, Entity, EntityMapper}, error::{BevyError, Result}, event::{Event, EventMutator, EventReader, EventWriter, Events}, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, name::{Name, NameOrEntity}, observer::{Observer, Trigger}, - query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, + query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, related, relationship::RelationshipTarget, removal_detection::RemovedComponents, resource::Resource, schedule::{ - apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoScheduleConfigs, - IntoSystemSet, Schedule, Schedules, SystemSet, + common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule, + Schedules, SystemCondition, SystemSet, }, spawn::{Spawn, SpawnRelated}, system::{ Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem, Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder, - SystemParamFunction, WithParamWarnPolicy, + SystemParamFunction, When, }, world::{ EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut, @@ -133,7 +129,7 @@ mod tests { bundle::Bundle, change_detection::Ref, component::{Component, ComponentId, RequiredComponents, RequiredComponentsError}, - entity::Entity, + entity::{Entity, EntityMapper}, entity_disabling::DefaultQueryFilters, prelude::Or, query::{Added, Changed, FilteredAccess, QueryFilter, With, Without}, @@ -146,13 +142,11 @@ mod tests { vec, vec::Vec, }; - use bevy_ecs_macros::{VisitEntities, VisitEntitiesMut}; - use bevy_platform_support::collections::HashSet; + use bevy_platform::collections::HashSet; use bevy_tasks::{ComputeTaskPool, TaskPool}; use core::{ any::TypeId, marker::PhantomData, - num::NonZero, sync::atomic::{AtomicUsize, Ordering}, }; use std::sync::Mutex; @@ -489,10 +483,9 @@ mod tests { results.lock().unwrap().push((e, i)); }); results.lock().unwrap().sort(); - assert_eq!( - &*results.lock().unwrap(), - &[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] - ); + let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]; + expected.sort(); + assert_eq!(&*results.lock().unwrap(), &expected); } #[test] @@ -510,10 +503,9 @@ mod tests { .par_iter(&world) .for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i))); results.lock().unwrap().sort(); - assert_eq!( - &*results.lock().unwrap(), - &[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] - ); + let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]; + expected.sort(); + assert_eq!(&*results.lock().unwrap(), &expected); } #[test] @@ -1236,7 +1228,6 @@ mod tests { .components() .get_resource_id(TypeId::of::()) .unwrap(); - let archetype_component_id = world.storages().resources.get(resource_id).unwrap().id(); assert_eq!(world.resource::().0, 123); assert!(world.contains_resource::()); @@ -1298,14 +1289,6 @@ mod tests { resource_id, current_resource_id, "resource id does not change after removing / re-adding" ); - - let current_archetype_component_id = - world.storages().resources.get(resource_id).unwrap().id(); - - assert_eq!( - archetype_component_id, current_archetype_component_id, - "resource archetype component id does not change after removing / re-adding" - ); } #[test] @@ -1543,8 +1526,8 @@ mod tests { let mut world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); - let _ = query.get(&world_a, Entity::from_raw(0)); - let _ = query.get(&world_b, Entity::from_raw(0)); + let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap()); + let _ = query.get(&world_b, Entity::from_raw_u32(0).unwrap()); } #[test] @@ -1697,97 +1680,6 @@ mod tests { assert_eq!(0, query_min_size![(&A, &B), Or<(Changed, Changed)>]); } - #[test] - fn insert_or_spawn_batch() { - let mut world = World::default(); - let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); - - let values = vec![(e0, (B(0), C)), (e1, (B(1), C))]; - - #[expect( - deprecated, - reason = "This needs to be supported for now, and therefore still needs the test." - )] - world.insert_or_spawn_batch(values).unwrap(); - - assert_eq!( - world.get::(e0), - Some(&A(0)), - "existing component was preserved" - ); - assert_eq!( - world.get::(e0), - Some(&B(0)), - "pre-existing entity received correct B component" - ); - assert_eq!( - world.get::(e1), - Some(&B(1)), - "new entity was spawned and received correct B component" - ); - assert_eq!( - world.get::(e0), - Some(&C), - "pre-existing entity received C component" - ); - assert_eq!( - world.get::(e1), - Some(&C), - "new entity was spawned and received C component" - ); - } - - #[test] - fn insert_or_spawn_batch_invalid() { - let mut world = World::default(); - let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); - let e2 = world.spawn_empty().id(); - let invalid_e2 = - Entity::from_raw_and_generation(e2.index(), NonZero::::new(2).unwrap()); - - let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))]; - - #[expect( - deprecated, - reason = "This needs to be supported for now, and therefore still needs the test." - )] - let result = world.insert_or_spawn_batch(values); - - assert_eq!( - result, - Err(vec![invalid_e2]), - "e2 failed to be spawned or inserted into" - ); - - assert_eq!( - world.get::(e0), - Some(&A(0)), - "existing component was preserved" - ); - assert_eq!( - world.get::(e0), - Some(&B(0)), - "pre-existing entity received correct B component" - ); - assert_eq!( - world.get::(e1), - Some(&B(1)), - "new entity was spawned and received correct B component" - ); - assert_eq!( - world.get::(e0), - Some(&C), - "pre-existing entity received C component" - ); - assert_eq!( - world.get::(e1), - Some(&C), - "new entity was spawned and received C component" - ); - } - #[test] fn insert_batch() { let mut world = World::default(); @@ -1876,7 +1768,7 @@ mod tests { fn try_insert_batch() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); + let e1 = Entity::from_raw_u32(1).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; @@ -1900,7 +1792,7 @@ mod tests { fn try_insert_batch_if_new() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); + let e1 = Entity::from_raw_u32(1).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; @@ -1927,7 +1819,7 @@ mod tests { struct X; #[derive(Component)] - #[require(Z(new_z))] + #[require(Z = new_z())] struct Y { value: String, } @@ -2652,7 +2544,7 @@ mod tests { struct MyRequired(bool); #[derive(Component, Default)] - #[require(MyRequired(|| MyRequired(false)))] + #[require(MyRequired(false))] struct MiddleMan; #[derive(Component, Default)] @@ -2660,7 +2552,7 @@ mod tests { struct ConflictingRequire; #[derive(Component, Default)] - #[require(MyRequired(|| MyRequired(true)))] + #[require(MyRequired(true))] struct MyComponent; let mut world = World::new(); @@ -2705,8 +2597,19 @@ mod tests { World::new().register_component::(); } + #[derive(Default)] + struct CaptureMapper(Vec); + impl EntityMapper for CaptureMapper { + fn get_mapped(&mut self, source: Entity) -> Entity { + self.0.push(source); + source + } + + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} + } + #[test] - fn visit_struct_entities() { + fn map_struct_entities() { #[derive(Component)] #[expect( unused, @@ -2733,30 +2636,22 @@ mod tests { let e3 = world.spawn_empty().id(); let mut foo = Foo(1, e1); - let mut entities = Vec::new(); - Component::visit_entities(&foo, |e| entities.push(e)); - assert_eq!(&entities, &[e1]); - - let mut entities = Vec::new(); - Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); - assert_eq!(&entities, &[e1]); + let mut mapper = CaptureMapper::default(); + Component::map_entities(&mut foo, &mut mapper); + assert_eq!(&mapper.0, &[e1]); let mut bar = Bar { a: e1, b: 1, c: vec![e2, e3], }; - let mut entities = Vec::new(); - Component::visit_entities(&bar, |e| entities.push(e)); - assert_eq!(&entities, &[e1, e2, e3]); - - let mut entities = Vec::new(); - Component::visit_entities_mut(&mut bar, |e| entities.push(*e)); - assert_eq!(&entities, &[e1, e2, e3]); + let mut mapper = CaptureMapper::default(); + Component::map_entities(&mut bar, &mut mapper); + assert_eq!(&mapper.0, &[e1, e2, e3]); } #[test] - fn visit_enum_entities() { + fn map_enum_entities() { #[derive(Component)] #[expect( unused, @@ -2779,26 +2674,18 @@ mod tests { let e3 = world.spawn_empty().id(); let mut foo = Foo::Bar(1, e1); - let mut entities = Vec::new(); - Component::visit_entities(&foo, |e| entities.push(e)); - assert_eq!(&entities, &[e1]); - - let mut entities = Vec::new(); - Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); - assert_eq!(&entities, &[e1]); + let mut mapper = CaptureMapper::default(); + Component::map_entities(&mut foo, &mut mapper); + assert_eq!(&mapper.0, &[e1]); let mut foo = Foo::Baz { a: e1, b: 1, c: vec![e2, e3], }; - let mut entities = Vec::new(); - Component::visit_entities(&foo, |e| entities.push(e)); - assert_eq!(&entities, &[e1, e2, e3]); - - let mut entities = Vec::new(); - Component::visit_entities_mut(&mut foo, |e| entities.push(*e)); - assert_eq!(&entities, &[e1, e2, e3]); + let mut mapper = CaptureMapper::default(); + Component::map_entities(&mut foo, &mut mapper); + assert_eq!(&mapper.0, &[e1, e2, e3]); } #[expect( @@ -2827,16 +2714,18 @@ mod tests { field1: ComponentB, } - #[derive(Component, VisitEntities, VisitEntitiesMut)] + #[derive(Component)] struct MyEntities { + #[entities] entities: Vec, + #[entities] another_one: Entity, + #[entities] maybe_entity: Option, #[expect( dead_code, reason = "This struct is used as a compilation test to test the derive macros, and as such this field is intentionally never used." )] - #[visit_entities(ignore)] something_else: String, } @@ -2844,6 +2733,29 @@ mod tests { dead_code, reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." )] - #[derive(Component, VisitEntities, VisitEntitiesMut)] - struct MyEntitiesTuple(Vec, Entity, #[visit_entities(ignore)] usize); + #[derive(Component)] + struct MyEntitiesTuple(#[entities] Vec, #[entities] Entity, usize); + + #[test] + fn clone_entities() { + use crate::entity::{ComponentCloneCtx, SourceComponent}; + + #[derive(Component)] + #[component(clone_behavior = Ignore)] + struct IgnoreClone; + + #[derive(Component)] + #[component(clone_behavior = Default)] + struct DefaultClone; + + #[derive(Component)] + #[component(clone_behavior = Custom(custom_clone))] + struct CustomClone; + + #[derive(Component, Clone)] + #[component(clone_behavior = clone::())] + struct CloneFunction; + + fn custom_clone(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {} + } } diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index ca94b4be80..cd2e946678 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -6,7 +6,7 @@ use alloc::{ borrow::{Cow, ToOwned}, string::String, }; -use bevy_platform_support::hash::FixedHasher; +use bevy_platform::hash::FixedHasher; use core::{ hash::{BuildHasher, Hash, Hasher}, ops::Deref, @@ -276,7 +276,7 @@ mod tests { let d1 = query.get(&world, e1).unwrap(); let d2 = query.get(&world, e2).unwrap(); // NameOrEntity Display for entities without a Name should be {index}v{generation} - assert_eq!(d1.to_string(), "0v1"); + assert_eq!(d1.to_string(), "0v0"); // NameOrEntity Display for entities with a Name should be the Name assert_eq!(d2.to_string(), "MyName"); } diff --git a/crates/bevy_ecs/src/never.rs b/crates/bevy_ecs/src/never.rs new file mode 100644 index 0000000000..ba814c7006 --- /dev/null +++ b/crates/bevy_ecs/src/never.rs @@ -0,0 +1,39 @@ +//! A workaround for the `!` type in stable Rust. +//! +//! This approach is taken from the [`never_say_never`] crate, +//! reimplemented here to avoid adding a new dependency. +//! +//! This module exists due to a change in [never type fallback inference] in the Rust 2024 edition. +//! This caused failures in `bevy_ecs`'s traits which are implemented for functions +//! (like [`System`](crate::system::System)) when working with panicking closures. +//! +//! Note that using this hack is not recommended in general; +//! by doing so you are knowingly opting out of rustc's stability guarantees. +//! Code that compiles due to this hack may break in future versions of Rust. +//! +//! Please read [issue #18778](https://github.com/bevyengine/bevy/issues/18778) for an explanation of why +//! Bevy has chosen to use this workaround. +//! +//! [`never_say_never`]: https://crates.io/crates/never_say_never +//! [never type fallback inference]: https://doc.rust-lang.org/edition-guide/rust-2024/never-type-fallback.html + +mod fn_ret { + /// A helper trait for naming the ! type. + #[doc(hidden)] + pub trait FnRet { + /// The return type of the function. + type Output; + } + + /// This blanket implementation allows us to name the never type, + /// by using the associated type of this trait for `fn() -> !`. + impl FnRet for fn() -> R { + type Output = R; + } +} + +/// A hacky type alias for the `!` (never) type. +/// +/// This knowingly opts out of rustc's stability guarantees. +/// Read the module documentation carefully before using this! +pub type Never = ! as fn_ret::FnRet>::Output; diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 32afa41cc8..2c2d42b1c9 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -2,13 +2,13 @@ use crate::{ component::{ Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, }, - entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, SourceComponent}, - observer::ObserverState, - system::Commands, + entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent}, world::World, }; use alloc::vec::Vec; +use super::Observer; + /// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to. #[derive(Default)] pub struct ObservedBy(pub(crate) Vec); @@ -28,7 +28,7 @@ impl Component for ObservedBy { let Ok(mut entity_mut) = world.get_entity_mut(e) else { continue; }; - let Some(mut state) = entity_mut.get_mut::() else { + let Some(mut state) = entity_mut.get_mut::() else { continue; }; state.despawned_watched_entities += 1; @@ -64,15 +64,11 @@ impl EntityClonerBuilder<'_> { } } -fn component_clone_observed_by( - commands: &mut Commands, - _source: &SourceComponent, - ctx: &mut ComponentCloneCtx, -) { +fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentCloneCtx) { let target = ctx.target(); let source = ctx.source(); - commands.queue(move |world: &mut World| { + ctx.queue_deferred(move |world: &mut World, _mapper: &mut dyn EntityMapper| { let observed_by = world .get::(source) .map(|observed_by| observed_by.0.clone()) @@ -82,10 +78,10 @@ fn component_clone_observed_by( .entity_mut(target) .insert(ObservedBy(observed_by.clone())); - for observer in &observed_by { + for observer_entity in observed_by.iter().copied() { let mut observer_state = world - .get_mut::(*observer) - .expect("Source observer entity must have ObserverState"); + .get_mut::(observer_entity) + .expect("Source observer entity must have Observer"); observer_state.descriptor.entities.push(target); let event_types = observer_state.descriptor.events.clone(); let components = observer_state.descriptor.components.clone(); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 4bbd82c85b..767dc7ec95 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -11,13 +11,13 @@ use crate::{ archetype::ArchetypeFlags, change_detection::MaybeLocation, component::ComponentId, - entity::hash_map::EntityHashMap, + entity::EntityHashMap, prelude::*, system::IntoObserverSystem, world::{DeferredWorld, *}, }; use alloc::vec::Vec; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_ptr::Ptr; use core::{ fmt::Debug, @@ -315,13 +315,6 @@ impl ObserverDescriptor { self } - pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) { - self.events.extend(descriptor.events.iter().copied()); - self.components - .extend(descriptor.components.iter().copied()); - self.entities.extend(descriptor.entities.iter().copied()); - } - /// Returns the `events` that the observer is watching. pub fn events(&self) -> &[ComponentId] { &self.events @@ -349,7 +342,7 @@ pub struct ObserverTrigger { components: SmallVec<[ComponentId; 2]>, /// The entity the trigger targeted. pub target: Entity, - /// The location of the source code that triggered the obserer. + /// The location of the source code that triggered the observer. pub caller: MaybeLocation, } @@ -543,6 +536,8 @@ impl World { /// Spawns a "global" [`Observer`] which will watch for the given event. /// Returns its [`Entity`] as a [`EntityWorldMut`]. /// + /// `system` can be any system whose first parameter is a [`Trigger`]. + /// /// **Calling [`observe`](EntityWorldMut::observe) on the returned /// [`EntityWorldMut`] will observe the observer itself, which you very /// likely do not want.** @@ -562,6 +557,10 @@ impl World { /// // ... /// }); /// ``` + /// + /// # Panics + /// + /// Panics if the given system is an exclusive system. pub fn add_observer( &mut self, system: impl IntoObserverSystem, @@ -724,11 +723,10 @@ impl World { pub(crate) fn register_observer(&mut self, observer_entity: Entity) { // SAFETY: References do not alias. let (observer_state, archetypes, observers) = unsafe { - let observer_state: *const ObserverState = - self.get::(observer_entity).unwrap(); + let observer_state: *const Observer = self.get::(observer_entity).unwrap(); // Populate ObservedBy for each observed entity. - for watched_entity in &(*observer_state).descriptor.entities { - let mut entity_mut = self.entity_mut(*watched_entity); + for watched_entity in (*observer_state).descriptor.entities.iter().copied() { + let mut entity_mut = self.entity_mut(watched_entity); let mut observed_by = entity_mut.entry::().or_default().into_mut(); observed_by.0.push(observer_entity); } @@ -843,13 +841,13 @@ impl World { mod tests { use alloc::{vec, vec::Vec}; - use bevy_platform_support::collections::HashMap; + use bevy_platform::collections::HashMap; use bevy_ptr::OwningPtr; use crate::component::ComponentId; use crate::{ change_detection::MaybeLocation, - observer::{Observer, ObserverDescriptor, ObserverState, OnReplace}, + observer::{Observer, OnReplace}, prelude::*, traversal::Traversal, }; @@ -1079,7 +1077,7 @@ mod tests { world.add_observer(|_: Trigger, mut res: ResMut| res.observed("add_2")); world.spawn(A).flush(); - assert_eq!(vec!["add_1", "add_2"], world.resource::().0); + assert_eq!(vec!["add_2", "add_1"], world.resource::().0); // Our A entity plus our two observers assert_eq!(world.entities().len(), 3); } @@ -1364,14 +1362,14 @@ mod tests { world.init_resource::(); let event_a = OnRemove::register_component_id(&mut world); - world.spawn(ObserverState { - // SAFETY: we registered `event_a` above and it matches the type of EventA - descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) }, - runner: |mut world, _trigger, _ptr, _propagate| { + // SAFETY: we registered `event_a` above and it matches the type of EventA + let observe = unsafe { + Observer::with_dynamic_runner(|mut world, _trigger, _ptr, _propagate| { world.resource_mut::().observed("event_a"); - }, - ..Default::default() - }); + }) + .with_event(event_a) + }; + world.spawn(observe); world.commands().queue(move |world: &mut World| { // SAFETY: we registered `event_a` above and it matches the type of EventA @@ -1648,6 +1646,23 @@ mod tests { assert_eq!(vec!["event", "event"], world.resource::().0); } + // Originally for https://github.com/bevyengine/bevy/issues/18452 + #[test] + fn observer_modifies_relationship() { + fn on_add(trigger: Trigger, mut commands: Commands) { + commands + .entity(trigger.target()) + .with_related_entities::(|rsc| { + rsc.spawn_empty(); + }); + } + + let mut world = World::new(); + world.add_observer(on_add); + world.spawn(A); + world.flush(); + } + // Regression test for https://github.com/bevyengine/bevy/issues/14467 // Fails prior to https://github.com/bevyengine/bevy/pull/15398 #[test] diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 309f228b9b..520147d438 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,9 +1,9 @@ -use alloc::{boxed::Box, vec, vec::Vec}; +use alloc::{boxed::Box, vec}; use core::any::Any; use crate::{ component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, - error::{DefaultSystemErrorHandler, SystemErrorContext}, + error::{ErrorContext, ErrorHandler}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -12,85 +12,6 @@ use crate::{ }; use bevy_ptr::PtrMut; -/// Contains [`Observer`] information. This defines how a given observer behaves. It is the -/// "source of truth" for a given observer entity's behavior. -pub struct ObserverState { - pub(crate) descriptor: ObserverDescriptor, - pub(crate) runner: ObserverRunner, - pub(crate) last_trigger_id: u32, - pub(crate) despawned_watched_entities: u32, -} - -impl Default for ObserverState { - fn default() -> Self { - Self { - runner: |_, _, _, _| {}, - last_trigger_id: 0, - despawned_watched_entities: 0, - descriptor: Default::default(), - } - } -} - -impl ObserverState { - /// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`] - /// is triggered. - pub fn with_event(mut self, event: ComponentId) -> Self { - self.descriptor.events.push(event); - self - } - - /// Observe the given event list. This will cause the [`Observer`] to run whenever an event with any of the given [`ComponentId`]s - /// is triggered. - pub fn with_events(mut self, events: impl IntoIterator) -> Self { - self.descriptor.events.extend(events); - self - } - - /// Observe the given [`Entity`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered - /// for any [`Entity`] target in the list. - pub fn with_entities(mut self, entities: impl IntoIterator) -> Self { - self.descriptor.entities.extend(entities); - self - } - - /// Observe the given [`ComponentId`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered - /// for any [`ComponentId`] target in the list. - pub fn with_components(mut self, components: impl IntoIterator) -> Self { - self.descriptor.components.extend(components); - self - } -} - -impl Component for ObserverState { - const STORAGE_TYPE: StorageType = StorageType::SparseSet; - type Mutability = Mutable; - - fn on_add() -> Option { - Some(|mut world, HookContext { entity, .. }| { - world.commands().queue(move |world: &mut World| { - world.register_observer(entity); - }); - }) - } - - fn on_remove() -> Option { - Some(|mut world, HookContext { entity, .. }| { - let descriptor = core::mem::take( - &mut world - .entity_mut(entity) - .get_mut::() - .unwrap() - .as_mut() - .descriptor, - ); - world.commands().queue(move |world: &mut World| { - world.unregister_observer(entity, descriptor); - }); - }) - } -} - /// Type for function that is run when an observer is triggered. /// /// Typically refers to the default runner that runs the system stored in the associated [`Observer`] component, @@ -264,27 +185,71 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: /// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities! /// /// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`]. -/// -/// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and /// serves as the "source of truth" of the observer. /// /// [`SystemParam`]: crate::system::SystemParam pub struct Observer { - system: Box, - descriptor: ObserverDescriptor, hook_on_add: ComponentHook, - error_handler: Option, + error_handler: Option, + system: Box, + pub(crate) descriptor: ObserverDescriptor, + pub(crate) last_trigger_id: u32, + pub(crate) despawned_watched_entities: u32, + pub(crate) runner: ObserverRunner, } impl Observer { /// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered /// for _any_ entity (or no entity). + /// + /// # Panics + /// + /// Panics if the given system is an exclusive system. pub fn new>(system: I) -> Self { + let system = Box::new(IntoObserverSystem::into_system(system)); + assert!( + !system.is_exclusive(), + concat!( + "Exclusive system `{}` may not be used as observer.\n", + "Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do." + ), + system.name() + ); Self { - system: Box::new(IntoObserverSystem::into_system(system)), + system, descriptor: Default::default(), hook_on_add: hook_on_add::, error_handler: None, + runner: observer_system_runner::, + despawned_watched_entities: 0, + last_trigger_id: 0, + } + } + + /// Creates a new [`Observer`] with custom runner, this is mostly used for dynamic event observer + pub fn with_dynamic_runner(runner: ObserverRunner) -> Self { + Self { + system: Box::new(|| {}), + descriptor: Default::default(), + hook_on_add: |mut world, hook_context| { + let default_error_handler = world.default_error_handler(); + world.commands().queue(move |world: &mut World| { + let entity = hook_context.entity; + if let Some(mut observe) = world.get_mut::(entity) { + if observe.descriptor.events.is_empty() { + return; + } + if observe.error_handler.is_none() { + observe.error_handler = Some(default_error_handler); + } + world.register_observer(entity); + } + }); + }, + error_handler: None, + runner, + despawned_watched_entities: 0, + last_trigger_id: 0, } } @@ -322,7 +287,7 @@ impl Observer { /// Set the error handler to use for this observer. /// /// See the [`error` module-level documentation](crate::error) for more information. - pub fn with_error_handler(mut self, error_handler: fn(BevyError, SystemErrorContext)) -> Self { + pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self { self.error_handler = Some(error_handler); self } @@ -345,6 +310,21 @@ impl Component for Observer { hook(world, context); }) } + fn on_remove() -> Option { + Some(|mut world, HookContext { entity, .. }| { + let descriptor = core::mem::take( + &mut world + .entity_mut(entity) + .get_mut::() + .unwrap() + .as_mut() + .descriptor, + ); + world.commands().queue(move |world: &mut World| { + world.unregister_observer(entity, descriptor); + }); + }) + } } fn observer_system_runner>( @@ -360,12 +340,8 @@ fn observer_system_runner>( .get_entity(observer_trigger.observer) .debug_checked_unwrap() }; - // SAFETY: Observer was triggered so must have an `ObserverState` - let mut state = unsafe { - observer_cell - .get_mut::() - .debug_checked_unwrap() - }; + // SAFETY: Observer was triggered so must have an `Observer` + let mut state = unsafe { observer_cell.get_mut::().debug_checked_unwrap() }; // TODO: Move this check into the observer cache to avoid dynamic dispatch let last_trigger = world.last_trigger_id(); @@ -374,48 +350,57 @@ fn observer_system_runner>( } state.last_trigger_id = last_trigger; - // SAFETY: Observer was triggered so must have an `Observer` component. - let error_handler = unsafe { - observer_cell - .get::() - .debug_checked_unwrap() - .error_handler - .debug_checked_unwrap() - }; - let trigger: Trigger = Trigger::new( // SAFETY: Caller ensures `ptr` is castable to `&mut T` unsafe { ptr.deref_mut() }, propagate, observer_trigger, ); + // SAFETY: // - observer was triggered so must have an `Observer` component. // - observer cannot be dropped or mutated until after the system pointer is already dropped. let system: *mut dyn ObserverSystem = unsafe { - let mut observe = observer_cell.get_mut::().debug_checked_unwrap(); - let system = observe.system.downcast_mut::().unwrap(); + let system = state.system.downcast_mut::().debug_checked_unwrap(); &mut *system }; // SAFETY: - // - `update_archetype_component_access` is called first // - there are no outstanding references to world except a private component // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` + // and is never exclusive // - system is the same type erased system from above unsafe { - (*system).update_archetype_component_access(world); - if (*system).validate_param_unsafe(world) { - if let Err(err) = (*system).run_unsafe(trigger, world) { - error_handler( - err, - SystemErrorContext { - name: (*system).name(), - last_run: (*system).get_last_run(), - }, - ); - }; - (*system).queue_deferred(world.into_deferred()); + match (*system).validate_param_unsafe(world) { + Ok(()) => { + if let Err(err) = (*system).run_unsafe(trigger, world) { + let handler = state + .error_handler + .unwrap_or_else(|| world.default_error_handler()); + handler( + err, + ErrorContext::Observer { + name: (*system).name(), + last_run: (*system).get_last_run(), + }, + ); + }; + (*system).queue_deferred(world.into_deferred()); + } + Err(e) => { + if !e.skipped { + let handler = state + .error_handler + .unwrap_or_else(|| world.default_error_handler()); + handler( + e.into(), + ErrorContext::Observer { + name: (*system).name(), + last_run: (*system).get_last_run(), + }, + ); + } + } } } } @@ -434,52 +419,31 @@ fn hook_on_add>( ) { world.commands().queue(move |world: &mut World| { let event_id = E::register_component_id(world); - let mut components = Vec::new(); + let mut components = vec![]; B::component_ids(&mut world.components_registrator(), &mut |id| { components.push(id); }); - let mut descriptor = ObserverDescriptor { - events: vec![event_id], - components, - ..Default::default() - }; + if let Some(mut observe) = world.get_mut::(entity) { + observe.descriptor.events.push(event_id); + observe.descriptor.components.extend(components); - let error_handler = world.get_resource_or_init::().0; - - // Initialize System - let system: *mut dyn ObserverSystem = - if let Some(mut observe) = world.get_mut::(entity) { - descriptor.merge(&observe.descriptor); - if observe.error_handler.is_none() { - observe.error_handler = Some(error_handler); - } - let system = observe.system.downcast_mut::().unwrap(); - &mut *system - } else { - return; - }; - // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias - unsafe { - (*system).initialize(world); - } - - { - let mut entity = world.entity_mut(entity); - if let crate::world::Entry::Vacant(entry) = entity.entry::() { - entry.insert(ObserverState { - descriptor, - runner: observer_system_runner::, - ..Default::default() - }); + let system: *mut dyn ObserverSystem = observe.system.downcast_mut::().unwrap(); + // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias + unsafe { + (*system).initialize(world); } + world.register_observer(entity); } }); } - #[cfg(test)] mod tests { use super::*; - use crate::{event::Event, observer::Trigger}; + use crate::{ + error::{ignore, DefaultErrorHandler}, + event::Event, + observer::Trigger, + }; #[derive(Event)] struct TriggerEvent; @@ -507,12 +471,31 @@ mod tests { Err("I failed!".into()) } + // Using observer error handler let mut world = World::default(); world.init_resource::(); - let observer = Observer::new(system).with_error_handler(crate::error::ignore); - world.spawn(observer); - Schedule::default().run(&mut world); + world.spawn(Observer::new(system).with_error_handler(ignore)); + world.trigger(TriggerEvent); + assert!(world.resource::().0); + + // Using world error handler + let mut world = World::default(); + world.init_resource::(); + world.spawn(Observer::new(system)); + // Test that the correct handler is used when the observer was added + // before the default handler + world.insert_resource(DefaultErrorHandler(ignore)); world.trigger(TriggerEvent); assert!(world.resource::().0); } + + #[test] + #[should_panic( + expected = "Exclusive system `bevy_ecs::observer::runner::tests::exclusive_system_cannot_be_observer::system` may not be used as observer.\nInstead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do." + )] + fn exclusive_system_cannot_be_observer() { + fn system(_: Trigger, _world: &mut World) {} + let mut world = World::default(); + world.add_observer(system); + } } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 6591507892..9c63cb5a74 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -257,9 +257,10 @@ impl Access { /// This is for components whose values are not accessed (and thus will never cause conflicts), /// but whose presence in an archetype may affect query results. /// - /// Currently, this is only used for [`Has`]. + /// Currently, this is only used for [`Has`] and [`Allows`]. /// /// [`Has`]: crate::query::Has + /// [`Allows`]: crate::query::filter::Allows pub fn add_archetypal(&mut self, index: T) { self.archetypal.grow_and_insert(index.sparse_set_index()); } @@ -430,75 +431,56 @@ impl Access { /// Adds all access from `other`. pub fn extend(&mut self, other: &Access) { - let component_read_and_writes_inverted = - self.component_read_and_writes_inverted || other.component_read_and_writes_inverted; - let component_writes_inverted = - self.component_writes_inverted || other.component_writes_inverted; - - match ( - self.component_read_and_writes_inverted, + invertible_union_with( + &mut self.component_read_and_writes, + &mut self.component_read_and_writes_inverted, + &other.component_read_and_writes, other.component_read_and_writes_inverted, - ) { - (true, true) => { - self.component_read_and_writes - .intersect_with(&other.component_read_and_writes); - } - (true, false) => { - self.component_read_and_writes - .difference_with(&other.component_read_and_writes); - } - (false, true) => { - // We have to grow here because the new bits are going to get flipped to 1. - self.component_read_and_writes.grow( - self.component_read_and_writes - .len() - .max(other.component_read_and_writes.len()), - ); - self.component_read_and_writes.toggle_range(..); - self.component_read_and_writes - .intersect_with(&other.component_read_and_writes); - } - (false, false) => { - self.component_read_and_writes - .union_with(&other.component_read_and_writes); - } - } - - match ( - self.component_writes_inverted, + ); + invertible_union_with( + &mut self.component_writes, + &mut self.component_writes_inverted, + &other.component_writes, other.component_writes_inverted, - ) { - (true, true) => { - self.component_writes - .intersect_with(&other.component_writes); - } - (true, false) => { - self.component_writes - .difference_with(&other.component_writes); - } - (false, true) => { - // We have to grow here because the new bits are going to get flipped to 1. - self.component_writes.grow( - self.component_writes - .len() - .max(other.component_writes.len()), - ); - self.component_writes.toggle_range(..); - self.component_writes - .intersect_with(&other.component_writes); - } - (false, false) => { - self.component_writes.union_with(&other.component_writes); - } - } + ); self.reads_all_resources = self.reads_all_resources || other.reads_all_resources; self.writes_all_resources = self.writes_all_resources || other.writes_all_resources; - self.component_read_and_writes_inverted = component_read_and_writes_inverted; - self.component_writes_inverted = component_writes_inverted; self.resource_read_and_writes .union_with(&other.resource_read_and_writes); self.resource_writes.union_with(&other.resource_writes); + self.archetypal.union_with(&other.archetypal); + } + + /// Removes any access from `self` that would conflict with `other`. + /// This removes any reads and writes for any component written by `other`, + /// and removes any writes for any component read by `other`. + pub fn remove_conflicting_access(&mut self, other: &Access) { + invertible_difference_with( + &mut self.component_read_and_writes, + &mut self.component_read_and_writes_inverted, + &other.component_writes, + other.component_writes_inverted, + ); + invertible_difference_with( + &mut self.component_writes, + &mut self.component_writes_inverted, + &other.component_read_and_writes, + other.component_read_and_writes_inverted, + ); + + if other.reads_all_resources { + self.writes_all_resources = false; + self.resource_writes.clear(); + } + if other.writes_all_resources { + self.reads_all_resources = false; + self.resource_read_and_writes.clear(); + } + self.resource_read_and_writes + .difference_with(&other.resource_writes); + self.resource_writes + .difference_with(&other.resource_read_and_writes); } /// Returns `true` if the access and `other` can be active at the same time, @@ -838,6 +820,55 @@ impl Access { } } +/// Performs an in-place union of `other` into `self`, where either set may be inverted. +/// +/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, +/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`. +/// +/// This updates the `self` set to include any elements in the `other` set. +/// Note that this may change `self_inverted` to `true` if we add an infinite +/// set to a finite one, resulting in a new infinite set. +fn invertible_union_with( + self_set: &mut FixedBitSet, + self_inverted: &mut bool, + other_set: &FixedBitSet, + other_inverted: bool, +) { + match (*self_inverted, other_inverted) { + (true, true) => self_set.intersect_with(other_set), + (true, false) => self_set.difference_with(other_set), + (false, true) => { + *self_inverted = true; + // We have to grow here because the new bits are going to get flipped to 1. + self_set.grow(other_set.len()); + self_set.toggle_range(..); + self_set.intersect_with(other_set); + } + (false, false) => self_set.union_with(other_set), + } +} + +/// Performs an in-place set difference of `other` from `self`, where either set may be inverted. +/// +/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`, +/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`. +/// +/// This updates the `self` set to remove any elements in the `other` set. +/// Note that this may change `self_inverted` to `false` if we remove an +/// infinite set from another infinite one, resulting in a finite difference. +fn invertible_difference_with( + self_set: &mut FixedBitSet, + self_inverted: &mut bool, + other_set: &FixedBitSet, + other_inverted: bool, +) { + // We can share the implementation of `invertible_union_with` with some algebra: + // A - B = A & !B = !(!A | B) + *self_inverted = !*self_inverted; + invertible_union_with(self_set, self_inverted, other_set, other_inverted); + *self_inverted = !*self_inverted; +} + /// Error returned when attempting to iterate over items included in an [`Access`] /// if the access excludes items rather than including them. #[derive(Clone, Copy, PartialEq, Eq, Debug, Error)] @@ -952,7 +983,8 @@ impl AccessConflicts { } } - pub(crate) fn is_empty(&self) -> bool { + /// Returns true if there are no conflicts present + pub fn is_empty(&self) -> bool { match self { Self::All => false, Self::Individual(set) => set.is_empty(), @@ -968,11 +1000,10 @@ impl AccessConflicts { format!( "{}", ShortName( - world + &world .components - .get_info(ComponentId::get_sparse_set_index(index)) + .get_name(ComponentId::get_sparse_set_index(index)) .unwrap() - .name() ) ) }) @@ -1289,6 +1320,14 @@ impl Clone for FilteredAccessSet { } impl FilteredAccessSet { + /// Creates an empty [`FilteredAccessSet`]. + pub const fn new() -> Self { + Self { + combined_access: Access::new(), + filtered_accesses: Vec::new(), + } + } + /// Returns a reference to the unfiltered access of the entire set. #[inline] pub fn combined_access(&self) -> &Access { @@ -1412,15 +1451,13 @@ impl FilteredAccessSet { impl Default for FilteredAccessSet { fn default() -> Self { - Self { - combined_access: Default::default(), - filtered_accesses: Vec::new(), - } + Self::new() } } #[cfg(test)] mod tests { + use super::{invertible_difference_with, invertible_union_with}; use crate::query::{ access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess, FilteredAccessSet, UnboundedAccessError, @@ -1763,4 +1800,99 @@ mod tests { }), ); } + + /// Create a `FixedBitSet` with a given number of total bits and a given list of bits to set. + /// Setting the number of bits is important in tests since the `PartialEq` impl checks that the length matches. + fn bit_set(bits: usize, iter: impl IntoIterator) -> FixedBitSet { + let mut result = FixedBitSet::with_capacity(bits); + result.extend(iter); + result + } + + #[test] + fn invertible_union_with_tests() { + let invertible_union = |mut self_inverted: bool, other_inverted: bool| { + // Check all four possible bit states: In both sets, the first, the second, or neither + let mut self_set = bit_set(4, [0, 1]); + let other_set = bit_set(4, [0, 2]); + invertible_union_with( + &mut self_set, + &mut self_inverted, + &other_set, + other_inverted, + ); + (self_set, self_inverted) + }; + + // Check each combination of `inverted` flags + let (s, i) = invertible_union(false, false); + // [0, 1] | [0, 2] = [0, 1, 2] + assert_eq!((s, i), (bit_set(4, [0, 1, 2]), false)); + + let (s, i) = invertible_union(false, true); + // [0, 1] | [1, 3, ...] = [0, 1, 3, ...] + assert_eq!((s, i), (bit_set(4, [2]), true)); + + let (s, i) = invertible_union(true, false); + // [2, 3, ...] | [0, 2] = [0, 2, 3, ...] + assert_eq!((s, i), (bit_set(4, [1]), true)); + + let (s, i) = invertible_union(true, true); + // [2, 3, ...] | [1, 3, ...] = [1, 2, 3, ...] + assert_eq!((s, i), (bit_set(4, [0]), true)); + } + + #[test] + fn invertible_union_with_different_lengths() { + // When adding a large inverted set to a small normal set, + // make sure we invert the bits beyond the original length. + // Failing to call `grow` before `toggle_range` would cause bit 1 to be zero, + // which would incorrectly treat it as included in the output set. + let mut self_set = bit_set(1, [0]); + let mut self_inverted = false; + let other_set = bit_set(3, [0, 1]); + let other_inverted = true; + invertible_union_with( + &mut self_set, + &mut self_inverted, + &other_set, + other_inverted, + ); + + // [0] | [2, ...] = [0, 2, ...] + assert_eq!((self_set, self_inverted), (bit_set(3, [1]), true)); + } + + #[test] + fn invertible_difference_with_tests() { + let invertible_difference = |mut self_inverted: bool, other_inverted: bool| { + // Check all four possible bit states: In both sets, the first, the second, or neither + let mut self_set = bit_set(4, [0, 1]); + let other_set = bit_set(4, [0, 2]); + invertible_difference_with( + &mut self_set, + &mut self_inverted, + &other_set, + other_inverted, + ); + (self_set, self_inverted) + }; + + // Check each combination of `inverted` flags + let (s, i) = invertible_difference(false, false); + // [0, 1] - [0, 2] = [1] + assert_eq!((s, i), (bit_set(4, [1]), false)); + + let (s, i) = invertible_difference(false, true); + // [0, 1] - [1, 3, ...] = [0] + assert_eq!((s, i), (bit_set(4, [0]), false)); + + let (s, i) = invertible_difference(true, false); + // [2, 3, ...] - [0, 2] = [3, ...] + assert_eq!((s, i), (bit_set(4, [0, 1, 2]), true)); + + let (s, i) = invertible_difference(true, true); + // [2, 3, ...] - [1, 3, ...] = [2] + assert_eq!((s, i), (bit_set(4, [2]), false)); + } } diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 81819cb9ac..b545caad8f 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -248,11 +248,9 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { pub fn transmute_filtered( &mut self, ) -> &mut QueryBuilder<'w, NewD, NewF> { - let mut fetch_state = NewD::init_state(self.world); + let fetch_state = NewD::init_state(self.world); let filter_state = NewF::init_state(self.world); - NewD::set_access(&mut fetch_state, &self.access); - let mut access = FilteredAccess::default(); NewD::update_component_access(&fetch_state, &mut access); NewF::update_component_access(&filter_state, &mut access); @@ -275,7 +273,10 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { #[cfg(test)] mod tests { - use crate::{prelude::*, world::FilteredEntityRef}; + use crate::{ + prelude::*, + world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef}, + }; use std::dbg; #[derive(Component, PartialEq, Debug)] @@ -422,6 +423,89 @@ mod tests { } } + #[test] + fn builder_provide_access() { + let mut world = World::new(); + world.spawn((A(0), B(1))); + + let mut query = + QueryBuilder::<(Entity, FilteredEntityRef, FilteredEntityMut)>::new(&mut world) + .data::<&mut A>() + .data::<&B>() + .build(); + + // The `FilteredEntityRef` only has read access, so the `FilteredEntityMut` can have read access without conflicts + let (_entity, entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_2.get::().is_some()); + assert!(entity_ref_2.get_mut::().is_none()); + assert!(entity_ref_2.get::().is_some()); + assert!(entity_ref_2.get_mut::().is_none()); + + let mut query = + QueryBuilder::<(Entity, FilteredEntityMut, FilteredEntityMut)>::new(&mut world) + .data::<&mut A>() + .data::<&B>() + .build(); + + // The first `FilteredEntityMut` has write access to A, so the second one cannot have write access + let (_entity, mut entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get_mut::().is_some()); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get_mut::().is_none()); + assert!(entity_ref_2.get::().is_none()); + assert!(entity_ref_2.get_mut::().is_none()); + assert!(entity_ref_2.get::().is_some()); + assert!(entity_ref_2.get_mut::().is_none()); + + let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world) + .data::<&mut A>() + .data::<&mut B>() + .build(); + + // Any `A` access would conflict with `&mut A`, and write access to `B` would conflict with `&B`. + let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref.get::().is_none()); + assert!(entity_ref.get_mut::().is_none()); + assert!(entity_ref.get::().is_some()); + assert!(entity_ref.get_mut::().is_none()); + + let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world) + .data::() + .build(); + + // Same as above, but starting from "all" access + let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref.get::().is_none()); + assert!(entity_ref.get_mut::().is_none()); + assert!(entity_ref.get::().is_some()); + assert!(entity_ref.get_mut::().is_none()); + + let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept)>::new(&mut world) + .data::() + .build(); + + // Removing `EntityMutExcept` just leaves A + let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get_mut::().is_some()); + assert!(entity_ref_1.get::().is_none()); + assert!(entity_ref_1.get_mut::().is_none()); + + let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept)>::new(&mut world) + .data::() + .build(); + + // Removing `EntityRefExcept` just leaves A, plus read access + let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap(); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get_mut::().is_some()); + assert!(entity_ref_1.get::().is_some()); + assert!(entity_ref_1.get_mut::().is_none()); + } + /// Regression test for issue #14348 #[test] fn builder_static_dense_dynamic_sparse() { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index cd632f7b14..cffba8cda1 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -31,6 +31,8 @@ use variadics_please::all_tuples; /// Gets the identifier of the queried entity. /// - **[`EntityLocation`].** /// Gets the location metadata of the queried entity. +/// - **[`SpawnDetails`].** +/// Gets the tick the entity was spawned at. /// - **[`EntityRef`].** /// Read-only access to arbitrary components on the queried entity. /// - **[`EntityMut`].** @@ -291,6 +293,22 @@ pub unsafe trait QueryData: WorldQuery { /// This function manually implements subtyping for the query items. fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>; + /// Offers additional access above what we requested in `update_component_access`. + /// Implementations may add additional access that is a subset of `available_access` + /// and does not conflict with anything in `access`, + /// and must update `access` to include that access. + /// + /// This is used by [`WorldQuery`] types like [`FilteredEntityRef`] + /// and [`FilteredEntityMut`] to support dynamic access. + /// + /// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder) + fn provide_extra_access( + _state: &mut Self::State, + _access: &mut Access, + _available_access: &Access, + ) { + } + /// Fetch [`Self::Item`](`QueryData::Item`) for either the given `entity` in the current [`Table`], /// or for the given `entity` in the current [`Archetype`]. This must always be called after /// [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] or after @@ -322,7 +340,7 @@ pub type QueryItem<'w, Q> = ::Item<'w>; pub type ROQueryItem<'w, D> = QueryItem<'w, ::ReadOnly>; /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Entity { type Fetch<'w> = (); @@ -394,7 +412,7 @@ unsafe impl QueryData for Entity { unsafe impl ReadOnlyQueryData for Entity {} /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for EntityLocation { type Fetch<'w> = &'w Entities; @@ -470,12 +488,80 @@ unsafe impl QueryData for EntityLocation { /// SAFETY: access is read only unsafe impl ReadOnlyQueryData for EntityLocation {} -/// SAFETY: -/// `fetch` accesses all components in a readonly way. -/// This is sound because `update_component_access` and `update_archetype_component_access` set read access for all components and panic when appropriate. -/// Filters are unchanged. -unsafe impl<'a> WorldQuery for EntityRef<'a> { - type Fetch<'w> = UnsafeWorldCell<'w>; +/// The `SpawnDetails` query parameter fetches the [`Tick`] the entity was spawned at. +/// +/// To evaluate whether the spawn happened since the last time the system ran, the system +/// param [`SystemChangeTick`](bevy_ecs::system::SystemChangeTick) needs to be used. +/// +/// If the query should filter for spawned entities instead, use the +/// [`Spawned`](bevy_ecs::query::Spawned) query filter instead. +/// +/// # Examples +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::entity::Entity; +/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::query::Spawned; +/// # use bevy_ecs::query::SpawnDetails; +/// +/// fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) { +/// for (entity, spawn_details) in &query { +/// if spawn_details.is_spawned() { +/// print!("new "); +/// } +/// print!( +/// "entity {:?} spawned at {:?}", +/// entity, +/// spawn_details.spawned_at() +/// ); +/// match spawn_details.spawned_by().into_option() { +/// Some(location) => println!(" by {:?}", location), +/// None => println!() +/// } +/// } +/// } +/// +/// # bevy_ecs::system::assert_is_system(print_spawn_details); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct SpawnDetails { + spawned_by: MaybeLocation, + spawned_at: Tick, + last_run: Tick, + this_run: Tick, +} + +impl SpawnDetails { + /// Returns `true` if the entity spawned since the last time this system ran. + /// Otherwise, returns `false`. + pub fn is_spawned(self) -> bool { + self.spawned_at.is_newer_than(self.last_run, self.this_run) + } + + /// Returns the `Tick` this entity spawned at. + pub fn spawned_at(self) -> Tick { + self.spawned_at + } + + /// Returns the source code location from which this entity has been spawned. + pub fn spawned_by(self) -> MaybeLocation { + self.spawned_by + } +} + +#[doc(hidden)] +#[derive(Clone)] +pub struct SpawnDetailsFetch<'w> { + entities: &'w Entities, + last_run: Tick, + this_run: Tick, +} + +// SAFETY: +// No components are accessed. +unsafe impl WorldQuery for SpawnDetails { + type Fetch<'w> = SpawnDetailsFetch<'w>; type State = (); fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { @@ -485,10 +571,116 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { - world + SpawnDetailsFetch { + entities: world.entities(), + last_run, + this_run, + } + } + + const IS_DENSE: bool = true; + + #[inline] + unsafe fn set_archetype<'w>( + _fetch: &mut Self::Fetch<'w>, + _state: &Self::State, + _archetype: &'w Archetype, + _table: &'w Table, + ) { + } + + #[inline] + unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + } + + fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} + + fn init_state(_world: &mut World) {} + + fn get_state(_components: &Components) -> Option<()> { + Some(()) + } + + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + true + } +} + +// SAFETY: +// No components are accessed. +// Is its own ReadOnlyQueryData. +unsafe impl QueryData for SpawnDetails { + const IS_READ_ONLY: bool = true; + type ReadOnly = Self; + type Item<'w> = Self; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + // SAFETY: only living entities are queried + let (spawned_by, spawned_at) = unsafe { + fetch + .entities + .entity_get_spawned_or_despawned_unchecked(entity) + }; + Self { + spawned_by, + spawned_at, + last_run: fetch.last_run, + this_run: fetch.this_run, + } + } +} + +/// SAFETY: access is read only +unsafe impl ReadOnlyQueryData for SpawnDetails {} + +/// The [`WorldQuery::Fetch`] type for WorldQueries that can fetch multiple components from an entity +/// ([`EntityRef`], [`EntityMut`], etc.) +#[derive(Copy, Clone)] +#[doc(hidden)] +pub struct EntityFetch<'w> { + world: UnsafeWorldCell<'w>, + last_run: Tick, + this_run: Tick, +} + +/// SAFETY: +/// `fetch` accesses all components in a readonly way. +/// This is sound because `update_component_access` sets read access for all components and panic when appropriate. +/// Filters are unchanged. +unsafe impl<'a> WorldQuery for EntityRef<'a> { + type Fetch<'w> = EntityFetch<'w>; + type State = (); + + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fetch + } + + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _state: &Self::State, + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + EntityFetch { + world, + last_run, + this_run, + } } const IS_DENSE: bool = true; @@ -540,12 +732,17 @@ unsafe impl<'a> QueryData for EntityRef<'a> { #[inline(always)] unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w> { // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() }; + let cell = unsafe { + fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .debug_checked_unwrap() + }; // SAFETY: Read-only access to every component has been registered. unsafe { EntityRef::new(cell) } } @@ -556,7 +753,7 @@ unsafe impl ReadOnlyQueryData for EntityRef<'_> {} /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for EntityMut<'a> { - type Fetch<'w> = UnsafeWorldCell<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = (); fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { @@ -566,10 +763,14 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { - world + EntityFetch { + world, + last_run, + this_run, + } } const IS_DENSE: bool = true; @@ -621,12 +822,17 @@ unsafe impl<'a> QueryData for EntityMut<'a> { #[inline(always)] unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, + fetch: &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w> { // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() }; + let cell = unsafe { + fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .debug_checked_unwrap() + }; // SAFETY: mutable access to every component has been registered. unsafe { EntityMut::new(cell) } } @@ -634,8 +840,8 @@ unsafe impl<'a> QueryData for EntityMut<'a> { /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { - type Fetch<'w> = (UnsafeWorldCell<'w>, Access); - type State = FilteredAccess; + type Fetch<'w> = (EntityFetch<'w>, Access); + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -646,12 +852,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { let mut access = Access::default(); access.read_all_components(); - (world, access) + ( + EntityFetch { + world, + last_run, + this_run, + }, + access, + ) } #[inline] @@ -661,18 +874,12 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { _: &'w Archetype, _table: &Table, ) { - fetch.1.clone_from(&state.access); + fetch.1.clone_from(state); } #[inline] unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { - fetch.1.clone_from(&state.access); - } - - #[inline] - fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess) { - state.clone_from(access); - state.access_mut().clear_writes(); + fetch.1.clone_from(state); } fn update_component_access( @@ -680,18 +887,18 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { filtered_access: &mut FilteredAccess, ) { assert!( - filtered_access.access().is_compatible(&state.access), + filtered_access.access().is_compatible(state), "FilteredEntityRef conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", ); - filtered_access.access.extend(&state.access); + filtered_access.access.extend(state); } fn init_state(_world: &mut World) -> Self::State { - FilteredAccess::default() + Access::default() } fn get_state(_components: &Components) -> Option { - Some(FilteredAccess::default()) + Some(Access::default()) } fn matches_component_set( @@ -712,14 +919,38 @@ unsafe impl<'a> QueryData for FilteredEntityRef<'a> { item } + #[inline] + fn provide_extra_access( + state: &mut Self::State, + access: &mut Access, + available_access: &Access, + ) { + // Claim any extra access that doesn't conflict with other subqueries + // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` + // Start with the entire available access, since that is the most we can possibly access + state.clone_from(available_access); + // Prevent all writes, since `FilteredEntityRef` only performs read access + state.clear_writes(); + // Prevent any access that would conflict with other accesses in the current query + state.remove_conflicting_access(access); + // Finally, add the resulting access to the query access + // to make sure a later `FilteredEntityMut` won't conflict with this. + access.extend(state); + } + #[inline(always)] unsafe fn fetch<'w>( - (world, access): &mut Self::Fetch<'w>, + (fetch, access): &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w> { // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() }; + let cell = unsafe { + fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .debug_checked_unwrap() + }; // SAFETY: mutable access to every component has been registered. unsafe { FilteredEntityRef::new(cell, access.clone()) } } @@ -730,8 +961,8 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {} /// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self` unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { - type Fetch<'w> = (UnsafeWorldCell<'w>, Access); - type State = FilteredAccess; + type Fetch<'w> = (EntityFetch<'w>, Access); + type State = Access; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch @@ -742,12 +973,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { let mut access = Access::default(); access.write_all_components(); - (world, access) + ( + EntityFetch { + world, + last_run, + this_run, + }, + access, + ) } #[inline] @@ -757,17 +995,12 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { _: &'w Archetype, _table: &Table, ) { - fetch.1.clone_from(&state.access); + fetch.1.clone_from(state); } #[inline] unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { - fetch.1.clone_from(&state.access); - } - - #[inline] - fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess) { - state.clone_from(access); + fetch.1.clone_from(state); } fn update_component_access( @@ -775,18 +1008,18 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { filtered_access: &mut FilteredAccess, ) { assert!( - filtered_access.access().is_compatible(&state.access), + filtered_access.access().is_compatible(state), "FilteredEntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.", ); - filtered_access.access.extend(&state.access); + filtered_access.access.extend(state); } fn init_state(_world: &mut World) -> Self::State { - FilteredAccess::default() + Access::default() } fn get_state(_components: &Components) -> Option { - Some(FilteredAccess::default()) + Some(Access::default()) } fn matches_component_set( @@ -807,14 +1040,36 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { item } + #[inline] + fn provide_extra_access( + state: &mut Self::State, + access: &mut Access, + available_access: &Access, + ) { + // Claim any extra access that doesn't conflict with other subqueries + // This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder` + // Start with the entire available access, since that is the most we can possibly access + state.clone_from(available_access); + // Prevent any access that would conflict with other accesses in the current query + state.remove_conflicting_access(access); + // Finally, add the resulting access to the query access + // to make sure a later `FilteredEntityRef` or `FilteredEntityMut` won't conflict with this. + access.extend(state); + } + #[inline(always)] unsafe fn fetch<'w>( - (world, access): &mut Self::Fetch<'w>, + (fetch, access): &mut Self::Fetch<'w>, entity: Entity, _table_row: TableRow, ) -> Self::Item<'w> { // SAFETY: `fetch` must be called with an entity that exists in the world - let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() }; + let cell = unsafe { + fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .debug_checked_unwrap() + }; // SAFETY: mutable access to every component has been registered. unsafe { FilteredEntityMut::new(cell, access.clone()) } } @@ -827,7 +1082,7 @@ unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> where B: Bundle, { - type Fetch<'w> = UnsafeWorldCell<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { @@ -837,10 +1092,14 @@ where unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _: &Self::State, - _: Tick, - _: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { - world + EntityFetch { + world, + last_run, + this_run, + } } const IS_DENSE: bool = true; @@ -907,11 +1166,14 @@ where } unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, + fetch: &mut Self::Fetch<'w>, entity: Entity, _: TableRow, ) -> Self::Item<'w> { - let cell = world.get_entity(entity).unwrap(); + let cell = fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .unwrap(); EntityRefExcept::new(cell) } } @@ -927,7 +1189,7 @@ unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> where B: Bundle, { - type Fetch<'w> = UnsafeWorldCell<'w>; + type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { @@ -937,10 +1199,14 @@ where unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _: &Self::State, - _: Tick, - _: Tick, + last_run: Tick, + this_run: Tick, ) -> Self::Fetch<'w> { - world + EntityFetch { + world, + last_run, + this_run, + } } const IS_DENSE: bool = true; @@ -1008,17 +1274,20 @@ where } unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, + fetch: &mut Self::Fetch<'w>, entity: Entity, _: TableRow, ) -> Self::Item<'w> { - let cell = world.get_entity(entity).unwrap(); + let cell = fetch + .world + .get_entity_with_ticks(entity, fetch.last_run, fetch.this_run) + .unwrap(); EntityMutExcept::new(cell) } } /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for &Archetype { type Fetch<'w> = (&'w Entities, &'w Archetypes); @@ -1117,7 +1386,7 @@ impl Copy for ReadFetch<'_, T> {} /// SAFETY: /// `fetch` accesses a single component in a readonly way. -/// This is sound because `update_component_access` and `update_archetype_component_access` add read access for that component and panic when appropriate. +/// This is sound because `update_component_access` adds read access for that component and panic when appropriate. /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl WorldQuery for &T { @@ -1140,7 +1409,7 @@ unsafe impl WorldQuery for &T { || None, || { // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. + // which we are allowed to access since we registered it in `update_component_access`. // Note that we do not actually access any components in this function, we just get a shared // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } @@ -1236,7 +1505,7 @@ unsafe impl QueryData for &T { // SAFETY: set_table was previously called let table = unsafe { table.debug_checked_unwrap() }; // SAFETY: Caller ensures `table_row` is in range. - let item = unsafe { table.get(table_row.as_usize()) }; + let item = unsafe { table.get(table_row.index()) }; item.deref() }, |sparse_set| { @@ -1284,7 +1553,7 @@ impl Copy for RefFetch<'_, T> {} /// SAFETY: /// `fetch` accesses a single component in a readonly way. -/// This is sound because `update_component_access` and `update_archetype_component_access` add read access for that component and panic when appropriate. +/// This is sound because `update_component_access` adds read access for that component and panic when appropriate. /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { @@ -1307,7 +1576,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { || None, || { // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. + // which we are allowed to access since we registered it in `update_component_access`. // Note that we do not actually access any components in this function, we just get a shared // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } @@ -1348,11 +1617,15 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { ) { let column = table.get_column(component_id).debug_checked_unwrap(); let table_data = Some(( - column.get_data_slice(table.entity_count()).into(), - column.get_added_ticks_slice(table.entity_count()).into(), - column.get_changed_ticks_slice(table.entity_count()).into(), + column.get_data_slice(table.entity_count() as usize).into(), column - .get_changed_by_slice(table.entity_count()) + .get_added_ticks_slice(table.entity_count() as usize) + .into(), + column + .get_changed_ticks_slice(table.entity_count() as usize) + .into(), + column + .get_changed_by_slice(table.entity_count() as usize) .map(Into::into), )); // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table @@ -1410,13 +1683,13 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. - let component = unsafe { table_components.get(table_row.as_usize()) }; + let component = unsafe { table_components.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let added = unsafe { added_ticks.get(table_row.as_usize()) }; + let added = unsafe { added_ticks.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let changed = unsafe { changed_ticks.get(table_row.as_usize()) }; + let changed = unsafe { changed_ticks.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let caller = callers.map(|callers| unsafe { callers.get(table_row.as_usize()) }); + let caller = callers.map(|callers| unsafe { callers.get(table_row.index()) }); Ref { value: component.deref(), @@ -1479,7 +1752,7 @@ impl Copy for WriteFetch<'_, T> {} /// SAFETY: /// `fetch` accesses a single component mutably. -/// This is sound because `update_component_access` and `update_archetype_component_access` add write access for that component and panic when appropriate. +/// This is sound because `update_component_access` adds write access for that component and panic when appropriate. /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { @@ -1502,7 +1775,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { || None, || { // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. + // which we are allowed to access since we registered it in `update_component_access`. // Note that we do not actually access any components in this function, we just get a shared // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } @@ -1543,11 +1816,15 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ) { let column = table.get_column(component_id).debug_checked_unwrap(); let table_data = Some(( - column.get_data_slice(table.entity_count()).into(), - column.get_added_ticks_slice(table.entity_count()).into(), - column.get_changed_ticks_slice(table.entity_count()).into(), + column.get_data_slice(table.entity_count() as usize).into(), column - .get_changed_by_slice(table.entity_count()) + .get_added_ticks_slice(table.entity_count() as usize) + .into(), + column + .get_changed_ticks_slice(table.entity_count() as usize) + .into(), + column + .get_changed_by_slice(table.entity_count() as usize) .map(Into::into), )); // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table @@ -1605,13 +1882,13 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. - let component = unsafe { table_components.get(table_row.as_usize()) }; + let component = unsafe { table_components.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let added = unsafe { added_ticks.get(table_row.as_usize()) }; + let added = unsafe { added_ticks.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let changed = unsafe { changed_ticks.get(table_row.as_usize()) }; + let changed = unsafe { changed_ticks.get(table_row.index()) }; // SAFETY: The caller ensures `table_row` is in range. - let caller = callers.map(|callers| unsafe { callers.get(table_row.as_usize()) }); + let caller = callers.map(|callers| unsafe { callers.get(table_row.index()) }); Mut { value: component.deref_mut(), @@ -1649,7 +1926,7 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T /// /// SAFETY: /// `fetch` accesses a single component mutably. -/// This is sound because `update_component_access` and `update_archetype_component_access` add write access for that component and panic when appropriate. +/// This is sound because `update_component_access` adds write access for that component and panic when appropriate. /// `update_component_access` adds a `With` filter for a component. /// This is sound because `matches_component_set` returns whether the set contains that component. unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { @@ -1766,7 +2043,7 @@ impl Clone for OptionFetch<'_, T> { /// SAFETY: /// `fetch` might access any components that `T` accesses. -/// This is sound because `update_component_access` and `update_archetype_component_access` add the same accesses as `T`. +/// This is sound because `update_component_access` adds the same accesses as `T`. /// Filters are unchanged. unsafe impl WorldQuery for Option { type Fetch<'w> = OptionFetch<'w, T>; @@ -1951,7 +2228,7 @@ impl core::fmt::Debug for Has { } /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Has { type Fetch<'w> = bool; @@ -2079,6 +2356,16 @@ macro_rules! impl_tuple_query_data { )*) } + #[inline] + fn provide_extra_access( + state: &mut Self::State, + access: &mut Access, + available_access: &Access, + ) { + let ($($name,)*) = state; + $($name::provide_extra_access($name, access, available_access);)* + } + #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, @@ -2119,7 +2406,7 @@ macro_rules! impl_anytuple_fetch { )] /// SAFETY: /// `fetch` accesses are a subset of the subqueries' accesses - /// This is sound because `update_component_access` and `update_archetype_component_access` adds accesses according to the implementations of all the subqueries. + /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. /// `update_component_access` replaces the filters with a disjunction where every element is a conjunction of the previous filters and the filters of one of the subqueries. /// This is sound because `matches_component_set` returns a disjunction of the results of the subqueries' implementations. unsafe impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { @@ -2285,7 +2572,7 @@ all_tuples!( pub(crate) struct NopWorldQuery(PhantomData); /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for NopWorldQuery { type Fetch<'w> = (); @@ -2355,7 +2642,7 @@ unsafe impl QueryData for NopWorldQuery { unsafe impl ReadOnlyQueryData for NopWorldQuery {} /// SAFETY: -/// `update_component_access` and `update_archetype_component_access` do nothing. +/// `update_component_access` does nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for PhantomData { type Fetch<'a> = (); @@ -2493,10 +2780,11 @@ impl Copy for StorageSwitch {} #[cfg(test)] mod tests { - use bevy_ecs_macros::QueryData; - use super::*; + use crate::change_detection::DetectChanges; use crate::system::{assert_is_system, Query}; + use bevy_ecs::prelude::Schedule; + use bevy_ecs_macros::QueryData; #[derive(Component)] pub struct A; @@ -2590,4 +2878,34 @@ mod tests { assert_is_system(client_system); } + + // Test that EntityRef::get_ref::() returns a Ref value with the correct + // ticks when the EntityRef was retrieved from a Query. + // See: https://github.com/bevyengine/bevy/issues/13735 + #[test] + fn test_entity_ref_query_with_ticks() { + #[derive(Component)] + pub struct C; + + fn system(query: Query) { + for entity_ref in &query { + if let Some(c) = entity_ref.get_ref::() { + if !c.is_added() { + panic!("Expected C to be added"); + } + } + } + } + + let mut world = World::new(); + let mut schedule = Schedule::default(); + schedule.add_systems(system); + world.spawn(C); + + // reset the change ticks + world.clear_trackers(); + + // we want EntityRef to use the change ticks of the system + schedule.run(&mut world); + } } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index e4e1f0fd66..eccc819ca7 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,7 +1,7 @@ use crate::{ archetype::Archetype, component::{Component, ComponentId, Components, StorageType, Tick}, - entity::Entity, + entity::{Entities, Entity}, query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World}, @@ -17,6 +17,8 @@ use variadics_please::all_tuples; /// [`With`] and [`Without`] filters can be applied to check if the queried entity does or does not contain a particular component. /// - **Change detection filters.** /// [`Added`] and [`Changed`] filters can be applied to detect component changes to an entity. +/// - **Spawned filter.** +/// [`Spawned`] filter can be applied to check if the queried entity was spawned recently. /// - **`QueryFilter` tuples.** /// If every element of a tuple implements `QueryFilter`, then the tuple itself also implements the same trait. /// This enables a single `Query` to filter over multiple conditions. @@ -555,6 +557,63 @@ all_tuples!( S ); +/// Allows a query to contain entities with the component `T`, bypassing [`DefaultQueryFilters`]. +/// +/// [`DefaultQueryFilters`]: crate::entity_disabling::DefaultQueryFilters +pub struct Allows(PhantomData); + +/// SAFETY: +/// `update_component_access` does not add any accesses. +/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. +/// `update_component_access` adds an archetypal filter for `T`. +/// This is sound because it doesn't affect the query +unsafe impl WorldQuery for Allows { + type Fetch<'w> = (); + type State = ComponentId; + + fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} + + #[inline] + unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {} + + // Even if the component is sparse, this implementation doesn't do anything with it + const IS_DENSE: bool = true; + + #[inline] + unsafe fn set_archetype(_: &mut (), _: &ComponentId, _: &Archetype, _: &Table) {} + + #[inline] + unsafe fn set_table(_: &mut (), _: &ComponentId, _: &Table) {} + + #[inline] + fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { + access.access_mut().add_archetypal(id); + } + + fn init_state(world: &mut World) -> ComponentId { + world.register_component::() + } + + fn get_state(components: &Components) -> Option { + components.component_id::() + } + + fn matches_component_set(_: &ComponentId, _: &impl Fn(ComponentId) -> bool) -> bool { + // Allows always matches + true + } +} + +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for Allows { + const IS_ARCHETYPAL: bool = true; + + #[inline(always)] + unsafe fn filter_fetch(_: &mut Self::Fetch<'_>, _: Entity, _: TableRow) -> bool { + true + } +} + /// A filter on a component that only retains results the first time after they have been added. /// /// A common use for this filter is one-time initialization. @@ -567,8 +626,8 @@ all_tuples!( /// # Deferred /// /// Note, that entity modifications issued with [`Commands`](crate::system::Commands) -/// are visible only after deferred operations are applied, -/// typically at the end of the schedule iteration. +/// are visible only after deferred operations are applied, typically after the system +/// that queued them. /// /// # Time complexity /// @@ -670,7 +729,7 @@ unsafe impl WorldQuery for Added { || None, || { // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. + // which we are allowed to access since we registered it in `update_component_access`. // Note that we do not actually access any components' ticks in this function, we just get a shared // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. unsafe { world.storages().sparse_sets.get(id) } @@ -758,7 +817,7 @@ unsafe impl QueryFilter for Added { // SAFETY: set_table was previously called let table = unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. - let tick = unsafe { table.get(table_row.as_usize()) }; + let tick = unsafe { table.get(table_row.index()) }; tick.deref().is_newer_than(fetch.last_run, fetch.this_run) }, @@ -792,9 +851,8 @@ unsafe impl QueryFilter for Added { /// # Deferred /// /// Note, that entity modifications issued with [`Commands`](crate::system::Commands) -/// (like entity creation or entity component addition or removal) -/// are visible only after deferred operations are applied, -/// typically at the end of the schedule iteration. +/// (like entity creation or entity component addition or removal) are visible only +/// after deferred operations are applied, typically after the system that queued them. /// /// # Time complexity /// @@ -897,7 +955,7 @@ unsafe impl WorldQuery for Changed { || None, || { // SAFETY: The underlying type associated with `component_id` is `T`, - // which we are allowed to access since we registered it in `update_archetype_component_access`. + // which we are allowed to access since we registered it in `update_component_access`. // Note that we do not actually access any components' ticks in this function, we just get a shared // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. unsafe { world.storages().sparse_sets.get(id) } @@ -986,7 +1044,7 @@ unsafe impl QueryFilter for Changed { // SAFETY: set_table was previously called let table = unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. - let tick = unsafe { table.get(table_row.as_usize()) }; + let tick = unsafe { table.get(table_row.index()) }; tick.deref().is_newer_than(fetch.last_run, fetch.this_run) }, @@ -1005,6 +1063,146 @@ unsafe impl QueryFilter for Changed { } } +/// A filter that only retains results the first time after the entity has been spawned. +/// +/// A common use for this filter is one-time initialization. +/// +/// To retain all results without filtering but still check whether they were spawned after the +/// system last ran, use [`SpawnDetails`](crate::query::SpawnDetails) instead. +/// +/// **Note** that this includes entities that spawned before the first time this Query was run. +/// +/// # Deferred +/// +/// Note, that entity spawns issued with [`Commands`](crate::system::Commands) +/// are visible only after deferred operations are applied, typically after the +/// system that queued them. +/// +/// # Time complexity +/// +/// `Spawned` is not [`ArchetypeFilter`], which practically means that if query matches million +/// entities, `Spawned` filter will iterate over all of them even if none of them were spawned. +/// +/// For example, these two systems are roughly equivalent in terms of performance: +/// +/// ``` +/// # use bevy_ecs::entity::Entity; +/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::query::Spawned; +/// # use bevy_ecs::query::SpawnDetails; +/// +/// fn system1(query: Query) { +/// for entity in &query { /* entity spawned */ } +/// } +/// +/// fn system2(query: Query<(Entity, SpawnDetails)>) { +/// for (entity, spawned) in &query { +/// if spawned.is_spawned() { /* entity spawned */ } +/// } +/// } +/// ``` +/// +/// # Examples +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # use bevy_ecs::query::Spawned; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; +/// # +/// # #[derive(Component, Debug)] +/// # struct Name {}; +/// +/// fn print_spawning_entities(query: Query<&Name, Spawned>) { +/// for name in &query { +/// println!("Entity spawned: {:?}", name); +/// } +/// } +/// +/// # bevy_ecs::system::assert_is_system(print_spawning_entities); +/// ``` +pub struct Spawned; + +#[doc(hidden)] +#[derive(Clone)] +pub struct SpawnedFetch<'w> { + entities: &'w Entities, + last_run: Tick, + this_run: Tick, +} + +// SAFETY: WorldQuery impl accesses no components or component ticks +unsafe impl WorldQuery for Spawned { + type Fetch<'w> = SpawnedFetch<'w>; + type State = (); + + fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { + fetch + } + + #[inline] + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _state: &(), + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + SpawnedFetch { + entities: world.entities(), + last_run, + this_run, + } + } + + const IS_DENSE: bool = true; + + #[inline] + unsafe fn set_archetype<'w>( + _fetch: &mut Self::Fetch<'w>, + _state: &(), + _archetype: &'w Archetype, + _table: &'w Table, + ) { + } + + #[inline] + unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &(), _table: &'w Table) {} + + #[inline] + fn update_component_access(_state: &(), _access: &mut FilteredAccess) {} + + fn init_state(_world: &mut World) {} + + fn get_state(_components: &Components) -> Option<()> { + Some(()) + } + + fn matches_component_set(_state: &(), _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool { + true + } +} + +// SAFETY: WorldQuery impl accesses no components or component ticks +unsafe impl QueryFilter for Spawned { + const IS_ARCHETYPAL: bool = false; + + #[inline(always)] + unsafe fn filter_fetch( + fetch: &mut Self::Fetch<'_>, + entity: Entity, + _table_row: TableRow, + ) -> bool { + // SAFETY: only living entities are queried + let spawned = unsafe { + fetch + .entities + .entity_get_spawned_or_despawned_unchecked(entity) + .1 + }; + spawned.is_newer_than(fetch.last_run, fetch.this_run) + } +} + /// A marker trait to indicate that the filter works at an archetype level. /// /// This is needed to implement [`ExactSizeIterator`] for @@ -1015,7 +1213,7 @@ unsafe impl QueryFilter for Changed { /// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types /// also implement the same trait. /// -/// [`Added`] and [`Changed`] works with entities, and therefore are not archetypal. As such +/// [`Added`], [`Changed`] and [`Spawned`] work with entities, and therefore are not archetypal. As such /// they do not implement [`ArchetypeFilter`]. #[diagnostic::on_unimplemented( message = "`{Self}` is not a valid `Query` filter based on archetype information", diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 1e5dca4fb1..baf0d72697 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -3,7 +3,7 @@ use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, bundle::Bundle, component::Tick, - entity::{Entities, Entity, EntityBorrow, EntitySet, EntitySetIterator}, + entity::{ContainsEntity, Entities, Entity, EntityEquivalent, EntitySet, EntitySetIterator}, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId}, storage::{Table, TableRow, Tables}, world::{ @@ -19,6 +19,7 @@ use core::{ mem::MaybeUninit, ops::Range, }; +use nonmax::NonMaxU32; /// An [`Iterator`] over query results of a [`Query`](crate::system::Query). /// @@ -136,7 +137,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { mut accum: B, func: &mut Func, storage: StorageId, - range: Option>, + range: Option>, ) -> B where Func: FnMut(B, D::Item<'w>) -> B, @@ -199,7 +200,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { mut accum: B, func: &mut Func, table: &'w Table, - rows: Range, + rows: Range, ) -> B where Func: FnMut(B, D::Item<'w>) -> B, @@ -207,10 +208,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { if table.is_empty() { return accum; } - debug_assert!( - rows.end <= u32::MAX as usize, - "TableRow is only valid up to u32::MAX" - ); D::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table); F::set_table( @@ -222,8 +219,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { let entities = table.entities(); for row in rows { // SAFETY: Caller assures `row` in range of the current archetype. - let entity = unsafe { entities.get_unchecked(row) }; - let row = TableRow::from_usize(row); + let entity = unsafe { entities.get_unchecked(row as usize) }; + // SAFETY: This is from an exclusive range, so it can't be max. + let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(row)) }; // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. @@ -254,7 +252,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { mut accum: B, func: &mut Func, archetype: &'w Archetype, - indices: Range, + indices: Range, ) -> B where Func: FnMut(B, D::Item<'w>) -> B, @@ -279,7 +277,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { let entities = archetype.entities(); for index in indices { // SAFETY: Caller assures `index` in range of the current archetype. - let archetype_entity = unsafe { entities.get_unchecked(index) }; + let archetype_entity = unsafe { entities.get_unchecked(index as usize) }; // SAFETY: set_archetype was called prior. // Caller assures `index` in range of the current archetype. @@ -315,7 +313,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { /// # Safety /// - all `indices` must be in `[0, archetype.len())`. /// - `archetype` must match D and F - /// - `archetype` must have the same length with it's table. + /// - `archetype` must have the same length as its table. /// - The query iteration must not be dense (i.e. `self.query_state.is_dense` must be false). #[inline] pub(super) unsafe fn fold_over_dense_archetype_range( @@ -323,7 +321,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { mut accum: B, func: &mut Func, archetype: &'w Archetype, - rows: Range, + rows: Range, ) -> B where Func: FnMut(B, D::Item<'w>) -> B, @@ -331,14 +329,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { if archetype.is_empty() { return accum; } - debug_assert!( - rows.end <= u32::MAX as usize, - "TableRow is only valid up to u32::MAX" - ); let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); debug_assert!( archetype.len() == table.entity_count(), - "archetype and it's table must have the same length. " + "archetype and its table must have the same length. " ); D::set_archetype( @@ -356,8 +350,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { let entities = table.entities(); for row in rows { // SAFETY: Caller assures `row` in range of the current archetype. - let entity = unsafe { *entities.get_unchecked(row) }; - let row = TableRow::from_usize(row); + let entity = unsafe { *entities.get_unchecked(row as usize) }; + // SAFETY: This is from an exclusive range, so it can't be max. + let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(row)) }; // SAFETY: set_table was called prior. // Caller assures `row` in range of the current archetype. @@ -895,7 +890,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> let max_size = self.cursor.max_remaining(self.tables, self.archetypes); let archetype_query = F::IS_ARCHETYPAL; let min_size = if archetype_query { max_size } else { 0 }; - (min_size, Some(max_size)) + (min_size as usize, Some(max_size as usize)) } #[inline] @@ -1117,7 +1112,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug /// Entities that don't match the query are skipped. /// /// This struct is created by the [`Query::iter_many`](crate::system::Query::iter_many) and [`Query::iter_many_mut`](crate::system::Query::iter_many_mut) methods. -pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> { +pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> +{ world: UnsafeWorldCell<'w>, entity_iter: I, entities: &'w Entities, @@ -1128,7 +1124,7 @@ pub struct QueryManyIter<'w, 's, D: QueryData, F: QueryFilter, I: Iterator, } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> +impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> QueryManyIter<'w, 's, D, F, I> { /// # Safety @@ -1167,7 +1163,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// It is always safe for shared access. #[inline(always)] unsafe fn fetch_next_aliased_unchecked( - entity_iter: impl Iterator, + entity_iter: impl Iterator, entities: &'w Entities, tables: &'w Tables, archetypes: &'w Archetypes, @@ -1720,7 +1716,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> } } -impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> +impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> QueryManyIter<'w, 's, D, F, I> { /// Get next result from the back of the query @@ -1746,7 +1742,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator> Iterator +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> Iterator for QueryManyIter<'w, 's, D, F, I> { type Item = D::Item<'w>; @@ -1775,8 +1771,13 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> - DoubleEndedIterator for QueryManyIter<'w, 's, D, F, I> +impl< + 'w, + 's, + D: ReadOnlyQueryData, + F: QueryFilter, + I: DoubleEndedIterator, + > DoubleEndedIterator for QueryManyIter<'w, 's, D, F, I> { #[inline(always)] fn next_back(&mut self) -> Option { @@ -1798,8 +1799,8 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: DoubleEndedIterator> FusedIterator - for QueryManyIter<'w, 's, D, F, I> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator> + FusedIterator for QueryManyIter<'w, 's, D, F, I> { } @@ -1809,7 +1810,7 @@ unsafe impl<'w, 's, F: QueryFilter, I: EntitySetIterator> EntitySetIterator { } -impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug +impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Debug for QueryManyIter<'w, 's, D, F, I> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -2269,7 +2270,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator .enumerate() .try_fold(0, |acc, (i, cursor)| { let n = cursor.max_remaining(self.tables, self.archetypes); - Some(acc + choose(n, K - i)?) + Some(acc + choose(n as usize, K - i)?) }); let archetype_query = F::IS_ARCHETYPAL; @@ -2311,9 +2312,9 @@ struct QueryIterationCursor<'w, 's, D: QueryData, F: QueryFilter> { fetch: D::Fetch<'w>, filter: F::Fetch<'w>, // length of the table or length of the archetype, depending on whether both `D`'s and `F`'s fetches are dense - current_len: usize, + current_len: u32, // either table row or archetype index, depending on whether both `D`'s and `F`'s fetches are dense - current_row: usize, + current_row: u32, } impl Clone for QueryIterationCursor<'_, '_, D, F> { @@ -2394,7 +2395,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { let index = self.current_row - 1; if self.is_dense { // SAFETY: This must have been called previously in `next` as `current_row > 0` - let entity = unsafe { self.table_entities.get_unchecked(index) }; + let entity = unsafe { self.table_entities.get_unchecked(index as usize) }; // SAFETY: // - `set_table` must have been called previously either in `next` or before it. // - `*entity` and `index` are in the current table. @@ -2402,12 +2403,14 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { Some(D::fetch( &mut self.fetch, *entity, - TableRow::from_usize(index), + // SAFETY: This is from an exclusive range, so it can't be max. + TableRow::new(NonMaxU32::new_unchecked(index)), )) } } else { // SAFETY: This must have been called previously in `next` as `current_row > 0` - let archetype_entity = unsafe { self.archetype_entities.get_unchecked(index) }; + let archetype_entity = + unsafe { self.archetype_entities.get_unchecked(index as usize) }; // SAFETY: // - `set_archetype` must have been called previously either in `next` or before it. // - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype. @@ -2428,9 +2431,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { /// /// Note that if `F::IS_ARCHETYPAL`, the return value /// will be **the exact count of remaining values**. - fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize { + fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> u32 { let ids = self.storage_id_iter.clone(); - let remaining_matched: usize = if self.is_dense { + let remaining_matched: u32 = if self.is_dense { // SAFETY: The if check ensures that storage_id_iter stores TableIds unsafe { ids.map(|id| tables[id.table_id].entity_count()).sum() } } else { @@ -2477,8 +2480,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // SAFETY: set_table was called prior. // `current_row` is a table row in range of the current table, because if it was not, then the above would have been executed. - let entity = unsafe { self.table_entities.get_unchecked(self.current_row) }; - let row = TableRow::from_usize(self.current_row); + let entity = + unsafe { self.table_entities.get_unchecked(self.current_row as usize) }; + // SAFETY: The row is less than the u32 len, so it must not be max. + let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(self.current_row)) }; if !F::filter_fetch(&mut self.filter, *entity, row) { self.current_row += 1; continue; @@ -2526,8 +2531,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { // SAFETY: set_archetype was called prior. // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let archetype_entity = - unsafe { self.archetype_entities.get_unchecked(self.current_row) }; + let archetype_entity = unsafe { + self.archetype_entities + .get_unchecked(self.current_row as usize) + }; if !F::filter_fetch( &mut self.filter, archetype_entity.id(), diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index c1744cbf24..eb5e001e4d 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -107,7 +107,7 @@ mod tests { use crate::{ archetype::Archetype, component::{Component, ComponentId, Components, Tick}, - prelude::{AnyOf, Changed, Entity, Or, QueryState, Res, ResMut, Resource, With, Without}, + prelude::{AnyOf, Changed, Entity, Or, QueryState, Resource, With, Without}, query::{ ArchetypeFilter, FilteredAccess, Has, QueryCombinationIter, QueryData, ReadOnlyQueryData, WorldQuery, @@ -818,7 +818,6 @@ mod tests { /// SAFETY: /// `update_component_access` adds resource read access for `R`. - /// `update_archetype_component_access` does nothing, as this accesses no components. unsafe impl WorldQuery for ReadsRData { type Fetch<'w> = (); type State = ComponentId; @@ -904,28 +903,4 @@ mod tests { fn system(_q1: Query>, _q2: Query>) {} assert_is_system(system); } - - #[test] - fn read_res_sets_archetype_component_access() { - let mut world = World::new(); - - fn read_query(_q: Query>) {} - let mut read_query = IntoSystem::into_system(read_query); - read_query.initialize(&mut world); - - fn read_res(_r: Res) {} - let mut read_res = IntoSystem::into_system(read_res); - read_res.initialize(&mut world); - - fn write_res(_r: ResMut) {} - let mut write_res = IntoSystem::into_system(write_res); - write_res.initialize(&mut world); - - assert!(read_query - .archetype_component_access() - .is_compatible(read_res.archetype_component_access())); - assert!(!read_query - .archetype_component_access() - .is_compatible(write_res.archetype_component_access())); - } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index e0a1a5b2f0..c685659260 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,7 +1,7 @@ use crate::{ batching::BatchingStrategy, component::Tick, - entity::{unique_vec::UniqueEntityVec, EntityBorrow, TrustedEntityBorrow}, + entity::{EntityEquivalent, UniqueEntityEquivalentVec}, world::unsafe_world_cell::UnsafeWorldCell, }; @@ -62,7 +62,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { /// query.par_iter().for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { /// **local_queue += 1; /// }); - /// + /// /// // collect value from every thread /// let entity_count: usize = queue.iter_mut().map(|v| *v).sum(); /// } @@ -130,7 +130,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { } #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> usize { + fn get_batch_size(&self, thread_count: usize) -> u32 { let max_items = || { let id_iter = self.state.matched_storage_ids.iter(); if self.state.is_dense { @@ -147,10 +147,11 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { .map(|id| unsafe { archetypes[id.archetype_id].len() }) .max() } + .map(|v| v as usize) .unwrap_or(0) }; self.batching_strategy - .calc_batch_size(max_items, thread_count) + .calc_batch_size(max_items, thread_count) as u32 } } @@ -160,7 +161,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { /// /// [`Entity`]: crate::entity::Entity /// [`Query::par_iter_many`]: crate::system::Query::par_iter_many -pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityBorrow> { +pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent> { pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, pub(crate) entity_list: Vec, @@ -169,7 +170,7 @@ pub struct QueryParManyIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityBorro pub(crate) batching_strategy: BatchingStrategy, } -impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync> QueryParManyIter<'w, 's, D, F, E> { /// Changes the batching strategy used when iterating. @@ -205,7 +206,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> /// use bevy_utils::Parallel; /// use crate::{bevy_ecs::prelude::{Component, Res, Resource, Entity}, bevy_ecs::system::Query}; /// # use core::slice; - /// use bevy_platform_support::prelude::Vec; + /// use bevy_platform::prelude::Vec; /// # fn some_expensive_operation(_item: &T) -> usize { /// # 0 /// # } @@ -232,7 +233,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> /// query.par_iter_many(&entities).for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { /// **local_queue += some_expensive_operation(item); /// }); - /// + /// /// // collect value from every thread /// let final_value: usize = queue.iter_mut().map(|v| *v).sum(); /// } @@ -301,9 +302,9 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> } #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> usize { + fn get_batch_size(&self, thread_count: usize) -> u32 { self.batching_strategy - .calc_batch_size(|| self.entity_list.len(), thread_count) + .calc_batch_size(|| self.entity_list.len(), thread_count) as u32 } } @@ -314,22 +315,17 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> /// [`EntitySet`]: crate::entity::EntitySet /// [`Query::par_iter_many_unique`]: crate::system::Query::par_iter_many_unique /// [`Query::par_iter_many_unique_mut`]: crate::system::Query::par_iter_many_unique_mut -pub struct QueryParManyUniqueIter< - 'w, - 's, - D: QueryData, - F: QueryFilter, - E: TrustedEntityBorrow + Sync, -> { +pub struct QueryParManyUniqueIter<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> +{ pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, - pub(crate) entity_list: UniqueEntityVec, + pub(crate) entity_list: UniqueEntityEquivalentVec, pub(crate) last_run: Tick, pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } -impl<'w, 's, D: QueryData, F: QueryFilter, E: TrustedEntityBorrow + Sync> +impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync> QueryParManyUniqueIter<'w, 's, D, F, E> { /// Changes the batching strategy used when iterating. @@ -363,7 +359,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: TrustedEntityBorrow + Sync> /// /// ``` /// use bevy_utils::Parallel; - /// use crate::{bevy_ecs::{prelude::{Component, Res, Resource, Entity}, entity::unique_vec::UniqueEntityVec, system::Query}}; + /// use crate::{bevy_ecs::{prelude::{Component, Res, Resource, Entity}, entity::UniqueEntityVec, system::Query}}; /// # use core::slice; /// # use crate::bevy_ecs::entity::UniqueEntityIter; /// # fn some_expensive_operation(_item: &T) -> usize { @@ -374,7 +370,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: TrustedEntityBorrow + Sync> /// struct T; /// /// #[derive(Resource)] - /// struct V(UniqueEntityVec); + /// struct V(UniqueEntityVec); /// /// impl<'a> IntoIterator for &'a V { /// // ... @@ -392,7 +388,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: TrustedEntityBorrow + Sync> /// query.par_iter_many_unique(&entities).for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { /// **local_queue += some_expensive_operation(item); /// }); - /// + /// /// // collect value from every thread /// let final_value: usize = queue.iter_mut().map(|v| *v).sum(); /// } @@ -461,8 +457,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: TrustedEntityBorrow + Sync> } #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] - fn get_batch_size(&self, thread_count: usize) -> usize { + fn get_batch_size(&self, thread_count: usize) -> u32 { self.batching_strategy - .calc_batch_size(|| self.entity_list.len(), thread_count) + .calc_batch_size(|| self.entity_list.len(), thread_count) as u32 } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 393971c4ac..836fefbdfd 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,17 +1,17 @@ use crate::{ - archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, + archetype::{Archetype, ArchetypeGeneration, ArchetypeId}, component::{ComponentId, Tick}, - entity::{unique_array::UniqueEntityArray, Entity, EntityBorrow, EntitySet}, + entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, prelude::FromWorld, - query::{Access, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, + query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, storage::{SparseSetIndex, TableId}, system::Query, world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] -use crate::entity::{unique_slice::UniqueEntitySlice, TrustedEntityBorrow}; +use crate::entity::UniqueEntityEquivalentSlice; use alloc::vec::Vec; use core::{fmt, ptr}; @@ -21,8 +21,8 @@ use log::warn; use tracing::Span; use super::{ - ComponentAccessKind, NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, - QueryManyIter, QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, + NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, + QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; /// An ID for either a table or an archetype. Used for Query iteration. @@ -175,40 +175,6 @@ impl QueryState { Some(state) } - /// Identical to `new`, but it populates the provided `access` with the matched results. - pub(crate) fn new_with_access( - world: &mut World, - access: &mut Access, - ) -> Self { - let mut state = Self::new_uninitialized(world); - for archetype in world.archetypes.iter() { - // SAFETY: The state was just initialized from the `world` above, and the archetypes being added - // come directly from the same world. - unsafe { - if state.new_archetype_internal(archetype) { - state.update_archetype_component_access(archetype, access); - } - } - } - state.archetype_generation = world.archetypes.generation(); - - // Resource access is not part of any archetype and must be handled separately - if state.component_access.access().has_read_all_resources() { - access.read_all_resources(); - } else { - for component_id in state.component_access.access().resource_reads() { - access.add_resource_read(world.initialize_resource_internal(component_id).id()); - } - } - - debug_assert!( - !state.component_access.access().has_any_resource_write(), - "Mutable resource access in queries is not allowed" - ); - - state - } - /// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet /// /// `new_archetype` and its variants must be called on all of the World's archetypes before the @@ -287,7 +253,14 @@ impl QueryState { pub fn from_builder(builder: &mut QueryBuilder) -> Self { let mut fetch_state = D::init_state(builder.world_mut()); let filter_state = F::init_state(builder.world_mut()); - D::set_access(&mut fetch_state, builder.access()); + + let mut component_access = FilteredAccess::default(); + D::update_component_access(&fetch_state, &mut component_access); + D::provide_extra_access( + &mut fetch_state, + component_access.access_mut(), + builder.access().access(), + ); let mut component_access = builder.access().clone(); @@ -452,8 +425,8 @@ impl QueryState { /// /// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)` /// where `n` is the number of *potential* matches. This can be notably expensive for queries that rely - /// on non-archetypal filters such as [`Added`] or [`Changed`] which must individually check each query - /// result for a match. + /// on non-archetypal filters such as [`Added`], [`Changed`] or [`Spawned`] which must individually check + /// each query result for a match. /// /// # Panics /// @@ -461,6 +434,7 @@ impl QueryState { /// /// [`Added`]: crate::query::Added /// [`Changed`]: crate::query::Changed + /// [`Spawned`]: crate::query::Spawned #[inline] pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool { self.validate_world(world.id()); @@ -538,7 +512,7 @@ impl QueryState { // SAFETY: The validate_world call ensures that the world is the same the QueryState // was initialized from. unsafe { - self.new_archetype_internal(archetype); + self.new_archetype(archetype); } } } else { @@ -573,7 +547,7 @@ impl QueryState { // SAFETY: The validate_world call ensures that the world is the same the QueryState // was initialized from. unsafe { - self.new_archetype_internal(archetype); + self.new_archetype(archetype); } } } @@ -605,32 +579,9 @@ impl QueryState { /// Update the current [`QueryState`] with information from the provided [`Archetype`] /// (if applicable, i.e. if the archetype has any intersecting [`ComponentId`] with the current [`QueryState`]). /// - /// The passed in `access` will be updated with any new accesses introduced by the new archetype. - /// /// # Safety /// `archetype` must be from the `World` this state was initialized from. - pub unsafe fn new_archetype( - &mut self, - archetype: &Archetype, - access: &mut Access, - ) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from. - let matches = unsafe { self.new_archetype_internal(archetype) }; - if matches { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from. - unsafe { self.update_archetype_component_access(archetype, access) }; - } - } - - /// Process the given [`Archetype`] to update internal metadata about the [`Table`](crate::storage::Table)s - /// and [`Archetype`]s that are matched by this query. - /// - /// Returns `true` if the given `archetype` matches the query. Otherwise, returns `false`. - /// If there is no match, then there is no need to update the query's [`FilteredAccess`]. - /// - /// # Safety - /// `archetype` must be from the `World` this state was initialized from. - unsafe fn new_archetype_internal(&mut self, archetype: &Archetype) -> bool { + pub unsafe fn new_archetype(&mut self, archetype: &Archetype) { if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id)) && F::matches_component_set(&self.filter_state, &|id| archetype.contains(id)) && self.matches_component_set(&|id| archetype.contains(id)) @@ -653,9 +604,6 @@ impl QueryState { }); } } - true - } else { - false } } @@ -672,57 +620,6 @@ impl QueryState { }) } - /// For the given `archetype`, adds any component accessed used by this query's underlying [`FilteredAccess`] to `access`. - /// - /// The passed in `access` will be updated with any new accesses introduced by the new archetype. - /// - /// # Safety - /// `archetype` must be from the `World` this state was initialized from. - pub unsafe fn update_archetype_component_access( - &mut self, - archetype: &Archetype, - access: &mut Access, - ) { - // As a fast path, we can iterate directly over the components involved - // if the `access` is finite. - if let Ok(iter) = self.component_access.access.try_iter_component_access() { - iter.for_each(|component_access| { - if let Some(id) = archetype.get_archetype_component_id(*component_access.index()) { - match component_access { - ComponentAccessKind::Archetypal(_) => {} - ComponentAccessKind::Shared(_) => { - access.add_component_read(id); - } - ComponentAccessKind::Exclusive(_) => { - access.add_component_write(id); - } - } - } - }); - - return; - } - - for (component_id, archetype_component_id) in - archetype.components_with_archetype_component_id() - { - if self - .component_access - .access - .has_component_read(component_id) - { - access.add_component_read(archetype_component_id); - } - if self - .component_access - .access - .has_component_write(component_id) - { - access.add_component_write(archetype_component_id); - } - } - } - /// Use this to transform a [`QueryState`] into a more generic [`QueryState`]. /// This can be useful for passing to another function that might take the more general form. /// See [`Query::transmute_lens`](crate::system::Query::transmute_lens) for more details. @@ -753,29 +650,27 @@ impl QueryState { let mut fetch_state = NewD::get_state(world.components()).expect("Could not create fetch_state, Please initialize all referenced components before transmuting."); let filter_state = NewF::get_state(world.components()).expect("Could not create filter_state, Please initialize all referenced components before transmuting."); - fn to_readonly(mut access: FilteredAccess) -> FilteredAccess { - access.access_mut().clear_writes(); - access - } - - let self_access = if D::IS_READ_ONLY && self.component_access.access().has_any_write() { + let mut self_access = self.component_access.clone(); + if D::IS_READ_ONLY { // The current state was transmuted from a mutable // `QueryData` to a read-only one. // Ignore any write access in the current state. - &to_readonly(self.component_access.clone()) - } else { - &self.component_access - }; + self_access.access_mut().clear_writes(); + } - NewD::set_access(&mut fetch_state, self_access); NewD::update_component_access(&fetch_state, &mut component_access); + NewD::provide_extra_access( + &mut fetch_state, + component_access.access_mut(), + self_access.access(), + ); let mut filter_component_access = FilteredAccess::default(); NewF::update_component_access(&filter_state, &mut filter_component_access); component_access.extend(&filter_component_access); assert!( - component_access.is_subset(self_access), + component_access.is_subset(&self_access), "Transmuted state for {} attempts to access terms that are not allowed by original state {}.", core::any::type_name::<(NewD, NewF)>(), core::any::type_name::<(D, F)>() ); @@ -787,7 +682,7 @@ impl QueryState { is_dense: self.is_dense, fetch_state, filter_state, - component_access: self.component_access.clone(), + component_access: self_access, matched_tables: self.matched_tables.clone(), matched_archetypes: self.matched_archetypes.clone(), #[cfg(feature = "trace")] @@ -881,8 +776,12 @@ impl QueryState { } } - NewD::set_access(&mut new_fetch_state, &joined_component_access); NewD::update_component_access(&new_fetch_state, &mut component_access); + NewD::provide_extra_access( + &mut new_fetch_state, + component_access.access_mut(), + joined_component_access.access(), + ); let mut new_filter_component_access = FilteredAccess::default(); NewF::update_component_access(&new_filter_state, &mut new_filter_component_access); @@ -984,7 +883,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// ``` @@ -1005,7 +904,7 @@ impl QueryState { /// # Examples /// /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, unique_array::UniqueEntityArray, unique_vec::UniqueEntityVec}}; + /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -1022,7 +921,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// ``` @@ -1078,7 +977,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1100,7 +999,7 @@ impl QueryState { /// returned instead. /// /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, unique_array::UniqueEntityArray, unique_vec::UniqueEntityVec}}; + /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -1124,7 +1023,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1273,7 +1172,7 @@ impl QueryState { /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many<'w, 's, EntityList: IntoIterator>( + pub fn iter_many<'w, 's, EntityList: IntoIterator>( &'s mut self, world: &'w World, entities: EntityList, @@ -1296,7 +1195,7 @@ impl QueryState { /// - [`iter_many`](Self::iter_many) to update archetypes. /// - [`iter_manual`](Self::iter_manual) to iterate over all query items. #[inline] - pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>( &'s self, world: &'w World, entities: EntityList, @@ -1309,7 +1208,7 @@ impl QueryState { /// Items are returned in the order of the list of entities. /// Entities that don't match the query are skipped. #[inline] - pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>( &'s mut self, world: &'w mut World, entities: EntityList, @@ -1452,7 +1351,7 @@ impl QueryState { /// /// # assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// # let wrong_entity = Entity::from_raw(57); + /// # let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// # let invalid_entity = world.spawn_empty().id(); /// /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1492,7 +1391,7 @@ impl QueryState { &self, init_accum: INIT, world: UnsafeWorldCell<'w>, - batch_size: usize, + batch_size: u32, func: FN, last_run: Tick, this_run: Tick, @@ -1506,7 +1405,7 @@ impl QueryState { use arrayvec::ArrayVec; bevy_tasks::ComputeTaskPool::get().scope(|scope| { - // SAFETY: We only access table data that has been registered in `self.archetype_component_access`. + // SAFETY: We only access table data that has been registered in `self.component_access`. let tables = unsafe { &world.storages().tables }; let archetypes = world.archetypes(); let mut batch_queue = ArrayVec::new(); @@ -1535,7 +1434,7 @@ impl QueryState { // submit single storage larger than batch_size let submit_single = |count, storage_id: StorageId| { - for offset in (0..count).step_by(batch_size) { + for offset in (0..count).step_by(batch_size as usize) { let mut func = func.clone(); let init_accum = init_accum.clone(); let len = batch_size.min(count - offset); @@ -1551,7 +1450,7 @@ impl QueryState { } }; - let storage_entity_count = |storage_id: StorageId| -> usize { + let storage_entity_count = |storage_id: StorageId| -> u32 { if self.is_dense { tables[storage_id.table_id].entity_count() } else { @@ -1606,22 +1505,22 @@ impl QueryState { &self, init_accum: INIT, world: UnsafeWorldCell<'w>, - entity_list: &UniqueEntitySlice, - batch_size: usize, + entity_list: &UniqueEntityEquivalentSlice, + batch_size: u32, mut func: FN, last_run: Tick, this_run: Tick, ) where FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, - E: TrustedEntityBorrow + Sync, + E: EntityEquivalent + Sync, { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter,QueryState::par_fold_init_unchecked_manual // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual bevy_tasks::ComputeTaskPool::get().scope(|scope| { - let chunks = entity_list.chunks_exact(batch_size); + let chunks = entity_list.chunks_exact(batch_size as usize); let remainder = chunks.remainder(); for batch in chunks { @@ -1670,21 +1569,21 @@ impl QueryState { init_accum: INIT, world: UnsafeWorldCell<'w>, entity_list: &[E], - batch_size: usize, + batch_size: u32, mut func: FN, last_run: Tick, this_run: Tick, ) where FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone, INIT: Fn() -> T + Sync + Send + Clone, - E: EntityBorrow + Sync, + E: EntityEquivalent + Sync, { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::par_fold_init_unchecked_manual // QueryState::par_many_fold_init_unchecked_manual, QueryState::par_many_unique_fold_init_unchecked_manual bevy_tasks::ComputeTaskPool::get().scope(|scope| { - let chunks = entity_list.chunks_exact(batch_size); + let chunks = entity_list.chunks_exact(batch_size as usize); let remainder = chunks.remainder(); for batch in chunks { @@ -1773,7 +1672,7 @@ impl QueryState { /// /// fn my_system(query: Query<&A>) -> Result { /// let a = query.single()?; - /// + /// /// // Do something with `a` /// Ok(()) /// } @@ -1791,16 +1690,6 @@ impl QueryState { self.query(world).single_inner() } - /// A deprecated alias for [`QueryState::single`]. - #[deprecated(since = "0.16.0", note = "Please use `single` instead.")] - #[inline] - pub fn get_single<'w>( - &mut self, - world: &'w World, - ) -> Result, QuerySingleError> { - self.single(world) - } - /// Returns a single mutable query result when there is exactly one entity matching /// the query. /// @@ -1818,15 +1707,6 @@ impl QueryState { self.query_mut(world).single_inner() } - /// A deprecated alias for [`QueryState::single_mut`]. - #[deprecated(since = "0.16.0", note = "Please use `single` instead.")] - pub fn get_single_mut<'w>( - &mut self, - world: &'w mut World, - ) -> Result, QuerySingleError> { - self.single_mut(world) - } - /// Returns a query result when there is exactly one entity matching the query. /// /// If the number of query results is not exactly one, a [`QuerySingleError`] is returned @@ -1894,7 +1774,7 @@ mod tests { let world_2 = World::new(); let mut query_state = world_1.query::(); - let _panics = query_state.get(&world_2, Entity::from_raw(0)); + let _panics = query_state.get(&world_2, Entity::from_raw_u32(0).unwrap()); } #[test] @@ -2062,12 +1942,12 @@ mod tests { fn can_transmute_filtered_entity() { let mut world = World::new(); let entity = world.spawn((A(0), B(1))).id(); - let query = - QueryState::<(Entity, &A, &B)>::new(&mut world).transmute::(&world); + let query = QueryState::<(Entity, &A, &B)>::new(&mut world) + .transmute::<(Entity, FilteredEntityRef)>(&world); let mut query = query; // Our result is completely untyped - let entity_ref = query.single(&world).unwrap(); + let (_entity, entity_ref) = query.single(&world).unwrap(); assert_eq!(entity, entity_ref.id()); assert_eq!(0, entity_ref.get::().unwrap().0); @@ -2285,11 +2165,11 @@ mod tests { let query_1 = QueryState::<&mut A>::new(&mut world); let query_2 = QueryState::<&mut B>::new(&mut world); - let mut new_query: QueryState = query_1.join(&world, &query_2); + let mut new_query: QueryState<(Entity, FilteredEntityMut)> = query_1.join(&world, &query_2); - let mut entity = new_query.single_mut(&mut world).unwrap(); - assert!(entity.get_mut::().is_some()); - assert!(entity.get_mut::().is_some()); + let (_entity, mut entity_mut) = new_query.single_mut(&mut world).unwrap(); + assert!(entity_mut.get_mut::().is_some()); + assert!(entity_mut.get_mut::().is_some()); } #[test] @@ -2315,6 +2195,10 @@ mod tests { let mut query = QueryState::>::new(&mut world); assert_eq!(3, query.iter(&world).count()); + // Allows should bypass the filter entirely + let mut query = QueryState::<(), Allows>::new(&mut world); + assert_eq!(3, query.iter(&world).count()); + // Other filters should still be respected let mut query = QueryState::, Without>::new(&mut world); assert_eq!(1, query.iter(&world).count()); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index da147770e0..a6bcbf58bd 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -13,11 +13,11 @@ use variadics_please::all_tuples; /// # Safety /// /// Implementor must ensure that -/// [`update_component_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`] +/// [`update_component_access`], [`QueryData::provide_extra_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`] /// obey the following: /// -/// - For each component mutably accessed by [`QueryData::fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic. -/// - For each component readonly accessed by [`QueryData::fetch`] or [`QueryFilter::filter_fetch`], [`update_component_access`] should add read access unless write access has already been added, in which case it should panic. +/// - For each component mutably accessed by [`QueryData::fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add write access unless read or write access has already been added, in which case it should panic. +/// - For each component readonly accessed by [`QueryData::fetch`] or [`QueryFilter::filter_fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add read access unless write access has already been added, in which case it should panic. /// - If `fetch` mutably accesses the same component twice, [`update_component_access`] should panic. /// - [`update_component_access`] may not add a `Without` filter for a component unless [`matches_component_set`] always returns `false` when the component set contains that component. /// - [`update_component_access`] may not add a `With` filter for a component unless [`matches_component_set`] always returns `false` when the component set doesn't contain that component. @@ -27,9 +27,11 @@ use variadics_please::all_tuples; /// - Each filter in that disjunction must be a conjunction of the corresponding element's filter with the previous `access` /// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access. /// - Mutable resource access is not allowed. +/// - Any access added during [`QueryData::provide_extra_access`] must be a subset of `available_access`, and must not conflict with any access in `access`. /// /// When implementing [`update_component_access`], note that `add_read` and `add_write` both also add a `With` filter, whereas `extend_access` does not change the filters. /// +/// [`QueryData::provide_extra_access`]: crate::query::QueryData::provide_extra_access /// [`QueryData::fetch`]: crate::query::QueryData::fetch /// [`QueryFilter::filter_fetch`]: crate::query::QueryFilter::filter_fetch /// [`init_fetch`]: Self::init_fetch @@ -101,12 +103,6 @@ pub unsafe trait WorldQuery { /// - `state` must be the [`State`](Self::State) that `fetch` was initialized with. unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table); - /// Sets available accesses for implementors with dynamic access such as [`FilteredEntityRef`](crate::world::FilteredEntityRef) - /// or [`FilteredEntityMut`](crate::world::FilteredEntityMut). - /// - /// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder) - fn set_access(_state: &mut Self::State, _access: &FilteredAccess) {} - /// Adds any component accesses used by this [`WorldQuery`] to `access`. /// /// Used to check which queries are disjoint and can run in parallel diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index 77b0587279..893e9b13fa 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -121,10 +121,8 @@ pub struct ReflectComponentFns { pub reflect: fn(FilteredEntityRef) -> Option<&dyn Reflect>, /// Function pointer implementing [`ReflectComponent::reflect_mut()`]. pub reflect_mut: fn(FilteredEntityMut) -> Option>, - /// Function pointer implementing [`ReflectComponent::visit_entities()`]. - pub visit_entities: fn(&dyn Reflect, &mut dyn FnMut(Entity)), - /// Function pointer implementing [`ReflectComponent::visit_entities_mut()`]. - pub visit_entities_mut: fn(&mut dyn Reflect, &mut dyn FnMut(&mut Entity)), + /// Function pointer implementing [`ReflectComponent::map_entities()`]. + pub map_entities: fn(&mut dyn Reflect, &mut dyn EntityMapper), /// Function pointer implementing [`ReflectComponent::reflect_unchecked_mut()`]. /// /// # Safety @@ -291,18 +289,9 @@ impl ReflectComponent { &self.0 } - /// Calls a dynamic version of [`Component::visit_entities`]. - pub fn visit_entities(&self, component: &dyn Reflect, func: &mut dyn FnMut(Entity)) { - (self.0.visit_entities)(component, func); - } - - /// Calls a dynamic version of [`Component::visit_entities_mut`]. - pub fn visit_entities_mut( - &self, - component: &mut dyn Reflect, - func: &mut dyn FnMut(&mut Entity), - ) { - (self.0.visit_entities_mut)(component, func); + /// Calls a dynamic version of [`Component::map_entities`]. + pub fn map_entities(&self, component: &mut dyn Reflect, func: &mut dyn EntityMapper) { + (self.0.map_entities)(component, func); } } @@ -330,19 +319,18 @@ impl FromType for ReflectComponent { apply_or_insert_mapped: |entity, reflected_component, registry, - mapper, + mut mapper, relationship_hook_mode| { - let map_fn = map_function(mapper); if C::Mutability::MUTABLE { // SAFETY: guard ensures `C` is a mutable component if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::() } { component.apply(reflected_component.as_partial_reflect()); - C::visit_entities_mut(&mut component, map_fn); + C::map_entities(&mut component, &mut mapper); } else { let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); - C::visit_entities_mut(&mut component, map_fn); + C::map_entities(&mut component, &mut mapper); entity .insert_with_relationship_hook_mode(component, relationship_hook_mode); } @@ -350,7 +338,7 @@ impl FromType for ReflectComponent { let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); - C::visit_entities_mut(&mut component, map_fn); + C::map_entities(&mut component, &mut mapper); entity.insert_with_relationship_hook_mode(component, relationship_hook_mode); } }, @@ -395,20 +383,10 @@ impl FromType for ReflectComponent { register_component: |world: &mut World| -> ComponentId { world.register_component::() }, - visit_entities: |reflect: &dyn Reflect, func: &mut dyn FnMut(Entity)| { - let component = reflect.downcast_ref::().unwrap(); - Component::visit_entities(component, func); - }, - visit_entities_mut: |reflect: &mut dyn Reflect, func: &mut dyn FnMut(&mut Entity)| { + map_entities: |reflect: &mut dyn Reflect, mut mapper: &mut dyn EntityMapper| { let component = reflect.downcast_mut::().unwrap(); - Component::visit_entities_mut(component, func); + Component::map_entities(component, &mut mapper); }, }) } } - -fn map_function(mapper: &mut dyn EntityMapper) -> impl FnMut(&mut Entity) + '_ { - move |entity: &mut Entity| { - *entity = mapper.get_mapped(*entity); - } -} diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 20c5e16c6d..15f48e234c 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -102,7 +102,7 @@ pub trait ReflectCommandExt { component: Box, ) -> &mut Self; - /// Removes from the entity the component or bundle with the given type name registered in [`AppTypeRegistry`]. + /// Removes from the entity the component or bundle with the given type path registered in [`AppTypeRegistry`]. /// /// If the type is a bundle, it will remove any components in that bundle regardless if the entity /// contains all the components. @@ -159,12 +159,12 @@ pub trait ReflectCommandExt { /// .remove_reflect(prefab.data.reflect_type_path().to_owned()); /// } /// ``` - fn remove_reflect(&mut self, component_type_name: impl Into>) -> &mut Self; + fn remove_reflect(&mut self, component_type_path: impl Into>) -> &mut Self; /// Same as [`remove_reflect`](ReflectCommandExt::remove_reflect), but using the `T` resource as type registry instead of /// `AppTypeRegistry`. fn remove_reflect_with_registry>( &mut self, - component_type_name: impl Into>, + component_type_path: impl Into>, ) -> &mut Self; } @@ -263,7 +263,7 @@ impl<'w> EntityWorldMut<'w> { self } - /// Removes from the entity the component or bundle with the given type name registered in [`AppTypeRegistry`]. + /// Removes from the entity the component or bundle with the given type path registered in [`AppTypeRegistry`]. /// /// If the type is a bundle, it will remove any components in that bundle regardless if the entity /// contains all the components. diff --git a/crates/bevy_ecs/src/reflect/mod.rs b/crates/bevy_ecs/src/reflect/mod.rs index 4d94945afc..b630f58719 100644 --- a/crates/bevy_ecs/src/reflect/mod.rs +++ b/crates/bevy_ecs/src/reflect/mod.rs @@ -17,7 +17,6 @@ mod entity_commands; mod from_world; mod map_entities; mod resource; -mod visit_entities; pub use bundle::{ReflectBundle, ReflectBundleFns}; pub use component::{ReflectComponent, ReflectComponentFns}; @@ -25,7 +24,6 @@ pub use entity_commands::ReflectCommandExt; pub use from_world::{ReflectFromWorld, ReflectFromWorldFns}; pub use map_entities::ReflectMapEntities; pub use resource::{ReflectResource, ReflectResourceFns}; -pub use visit_entities::{ReflectVisitEntities, ReflectVisitEntitiesMut}; /// A [`Resource`] storing [`TypeRegistry`] for /// type registrations relevant to a whole app. diff --git a/crates/bevy_ecs/src/reflect/visit_entities.rs b/crates/bevy_ecs/src/reflect/visit_entities.rs deleted file mode 100644 index 11f02612ba..0000000000 --- a/crates/bevy_ecs/src/reflect/visit_entities.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::entity::{Entity, VisitEntities, VisitEntitiesMut}; -use bevy_reflect::{FromReflect, FromType, PartialReflect}; - -/// For a reflected value, apply an operation to all contained entities. -/// -/// See [`VisitEntities`] for more details. -#[derive(Clone)] -pub struct ReflectVisitEntities { - visit_entities: fn(&dyn PartialReflect, &mut dyn FnMut(Entity)), -} - -impl ReflectVisitEntities { - /// A general method for applying an operation to all entities in a - /// reflected component. - pub fn visit_entities(&self, component: &dyn PartialReflect, f: &mut dyn FnMut(Entity)) { - (self.visit_entities)(component, f); - } -} - -impl FromType for ReflectVisitEntities { - fn from_type() -> Self { - ReflectVisitEntities { - visit_entities: |component, f| { - let concrete = C::from_reflect(component).unwrap(); - concrete.visit_entities(f); - }, - } - } -} - -/// For a reflected value, apply an operation to mutable references to all -/// contained entities. -/// -/// See [`VisitEntitiesMut`] for more details. -#[derive(Clone)] -pub struct ReflectVisitEntitiesMut { - visit_entities_mut: fn(&mut dyn PartialReflect, &mut dyn FnMut(&mut Entity)), -} - -impl ReflectVisitEntitiesMut { - /// A general method for applying an operation to all entities in a - /// reflected component. - pub fn visit_entities( - &self, - component: &mut dyn PartialReflect, - f: &mut dyn FnMut(&mut Entity), - ) { - (self.visit_entities_mut)(component, f); - } -} - -impl FromType for ReflectVisitEntitiesMut { - fn from_type() -> Self { - ReflectVisitEntitiesMut { - visit_entities_mut: |component, f| { - let mut concrete = C::from_reflect(component).unwrap(); - concrete.visit_entities_mut(f); - component.apply(&concrete); - }, - } - } -} diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 6afb462476..3522118fbc 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -13,11 +13,8 @@ pub use relationship_source_collection::*; use crate::{ component::{Component, HookContext, Mutable}, entity::{ComponentCloneCtx, Entity, SourceComponent}, - system::{ - command::HandleError, - entity_command::{self, CommandWithEntity}, - error_handler, Commands, - }, + error::{ignore, CommandWithEntity, HandleError}, + system::entity_command::{self}, world::{DeferredWorld, EntityWorldMut}, }; use log::warn; @@ -161,19 +158,21 @@ pub trait Relationship: Component + Sized { { relationship_target.collection_mut_risky().remove(entity); if relationship_target.len() == 0 { - if let Ok(mut entity) = world.commands().get_entity(target_entity) { + let command = |mut entity: EntityWorldMut| { // this "remove" operation must check emptiness because in the event that an identical // relationship is inserted on top, this despawn would result in the removal of that identical // relationship ... not what we want! - entity.queue(|mut entity: EntityWorldMut| { - if entity - .get::() - .is_some_and(RelationshipTarget::is_empty) - { - entity.remove::(); - } - }); - } + if entity + .get::() + .is_some_and(RelationshipTarget::is_empty) + { + entity.remove::(); + } + }; + + world + .commands() + .queue(command.with_entity(target_entity).handle_error_with(ignore)); } } } @@ -212,12 +211,14 @@ pub trait RelationshipTarget: Component + Sized { /// /// # Warning /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. + /// The collection should not contain duplicates. fn collection_mut_risky(&mut self) -> &mut Self::Collection; /// Creates a new [`RelationshipTarget`] from the given [`RelationshipTarget::Collection`]. /// /// # Warning /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. + /// The collection should not contain duplicates. fn from_collection_risky(collection: Self::Collection) -> Self; /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. @@ -230,7 +231,7 @@ pub trait RelationshipTarget: Component + Sized { commands.queue( entity_command::remove::() .with_entity(source_entity) - .handle_error_with(error_handler::silent()), + .handle_error_with(ignore), ); } else { warn!( @@ -255,7 +256,7 @@ pub trait RelationshipTarget: Component + Sized { commands.queue( entity_command::despawn() .with_entity(source_entity) - .handle_error_with(error_handler::silent()), + .handle_error_with(ignore), ); } else { warn!( @@ -304,7 +305,6 @@ pub trait RelationshipTarget: Component + Sized { /// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured /// to spawn recursively. pub fn clone_relationship_target( - _commands: &mut Commands, source: &SourceComponent, context: &mut ComponentCloneCtx, ) { @@ -389,4 +389,100 @@ mod tests { assert!(!world.entity(b).contains::()); assert!(!world.entity(b).contains::()); } + + #[test] + fn relationship_with_multiple_non_target_fields_compiles() { + #[derive(Component)] + #[relationship(relationship_target=Target)] + #[expect(dead_code, reason = "test struct")] + struct Source { + #[relationship] + target: Entity, + foo: u8, + bar: u8, + } + + #[derive(Component)] + #[relationship_target(relationship=Source)] + struct Target(Vec); + + // No assert necessary, looking to make sure compilation works with the macros + } + #[test] + fn relationship_target_with_multiple_non_target_fields_compiles() { + #[derive(Component)] + #[relationship(relationship_target=Target)] + struct Source(Entity); + + #[derive(Component)] + #[relationship_target(relationship=Source)] + #[expect(dead_code, reason = "test struct")] + struct Target { + #[relationship] + target: Vec, + foo: u8, + bar: u8, + } + + // No assert necessary, looking to make sure compilation works with the macros + } + + #[test] + fn parent_child_relationship_with_custom_relationship() { + use crate::prelude::ChildOf; + + #[derive(Component)] + #[relationship(relationship_target = RelTarget)] + struct Rel(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Rel)] + struct RelTarget(Entity); + + let mut world = World::new(); + + // Rel on Parent + // Despawn Parent + let mut commands = world.commands(); + let child = commands.spawn_empty().id(); + let parent = commands.spawn(Rel(child)).add_child(child).id(); + commands.entity(parent).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(world.get_entity(parent).is_err()); + + // Rel on Parent + // Despawn Child + let mut commands = world.commands(); + let child = commands.spawn_empty().id(); + let parent = commands.spawn(Rel(child)).add_child(child).id(); + commands.entity(child).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(!world.entity(parent).contains::()); + + // Rel on Child + // Despawn Parent + let mut commands = world.commands(); + let parent = commands.spawn_empty().id(); + let child = commands.spawn((ChildOf(parent), Rel(parent))).id(); + commands.entity(parent).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(world.get_entity(parent).is_err()); + + // Rel on Child + // Despawn Child + let mut commands = world.commands(); + let parent = commands.spawn_empty().id(); + let child = commands.spawn((ChildOf(parent), Rel(parent))).id(); + commands.entity(child).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(!world.entity(parent).contains::()); + } } diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index b13baf5f2a..5a23214463 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -1,16 +1,29 @@ use crate::{ bundle::Bundle, - entity::Entity, - relationship::{Relationship, RelationshipTarget}, + entity::{hash_set::EntityHashSet, Entity}, + relationship::{ + Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget, + }, system::{Commands, EntityCommands}, world::{EntityWorldMut, World}, }; -use alloc::vec::Vec; -use core::marker::PhantomData; +use bevy_platform::prelude::{Box, Vec}; +use core::{marker::PhantomData, mem}; + +use super::OrderedRelationshipSourceCollection; impl<'w> EntityWorldMut<'w> { + /// Spawns a entity related to this entity (with the `R` relationship) by taking a bundle + pub fn with_related(&mut self, bundle: impl Bundle) -> &mut Self { + let parent = self.id(); + self.world_scope(|world| { + world.spawn((bundle, R::from(parent))); + }); + self + } + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. - pub fn with_related( + pub fn with_related_entities( &mut self, func: impl FnOnce(&mut RelatedSpawner), ) -> &mut Self { @@ -34,6 +47,239 @@ impl<'w> EntityWorldMut<'w> { self } + /// Removes the relation `R` between this entity and all its related entities. + pub fn clear_related(&mut self) -> &mut Self { + self.remove::() + } + + /// Relates the given entities to this entity with the relation `R`, starting at this particular index. + /// + /// If the `related` has duplicates, a related entity will take the index of its last occurrence in `related`. + /// If the indices go out of bounds, they will be clamped into bounds. + /// This will not re-order existing related entities unless they are in `related`. + /// + /// # Example + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// + /// let mut world = World::new(); + /// let e0 = world.spawn_empty().id(); + /// let e1 = world.spawn_empty().id(); + /// let e2 = world.spawn_empty().id(); + /// let e3 = world.spawn_empty().id(); + /// let e4 = world.spawn_empty().id(); + /// + /// let mut main_entity = world.spawn_empty(); + /// main_entity.add_related::(&[e0, e1, e2, e2]); + /// main_entity.insert_related::(1, &[e0, e3, e4, e4]); + /// let main_id = main_entity.id(); + /// + /// let relationship_source = main_entity.get::().unwrap().collection(); + /// assert_eq!(relationship_source, &[e1, e0, e3, e2, e4]); + /// ``` + pub fn insert_related(&mut self, index: usize, related: &[Entity]) -> &mut Self + where + ::Collection: + OrderedRelationshipSourceCollection, + { + let id = self.id(); + self.world_scope(|world| { + for (offset, related) in related.iter().enumerate() { + let index = index.saturating_add(offset); + if world + .get::(*related) + .is_some_and(|relationship| relationship.get() == id) + { + world + .get_mut::(id) + .expect("hooks should have added relationship target") + .collection_mut_risky() + .place(*related, index); + } else { + world.entity_mut(*related).insert(R::from(id)); + world + .get_mut::(id) + .expect("hooks should have added relationship target") + .collection_mut_risky() + .place_most_recent(index); + } + } + }); + + self + } + + /// Removes the relation `R` between this entity and the given entities. + pub fn remove_related(&mut self, related: &[Entity]) -> &mut Self { + let id = self.id(); + self.world_scope(|world| { + for related in related { + if world + .get::(*related) + .is_some_and(|relationship| relationship.get() == id) + { + world.entity_mut(*related).remove::(); + } + } + }); + + self + } + + /// Replaces all the related entities with a new set of entities. + pub fn replace_related(&mut self, related: &[Entity]) -> &mut Self { + type Collection = + <::RelationshipTarget as RelationshipTarget>::Collection; + + if related.is_empty() { + self.remove::(); + + return self; + } + + let Some(mut existing_relations) = self.get_mut::() else { + return self.add_related::(related); + }; + + // We take the collection here so we can modify it without taking the component itself (this would create archetype move). + // SAFETY: We eventually return the correctly initialized collection into the target. + let mut existing_relations = mem::replace( + existing_relations.collection_mut_risky(), + Collection::::with_capacity(0), + ); + + let mut potential_relations = EntityHashSet::from_iter(related.iter().copied()); + + let id = self.id(); + self.world_scope(|world| { + for related in existing_relations.iter() { + if !potential_relations.remove(related) { + world.entity_mut(related).remove::(); + } + } + + for related in potential_relations { + // SAFETY: We'll manually be adjusting the contents of the parent to fit the final state. + world + .entity_mut(related) + .insert_with_relationship_hook_mode(R::from(id), RelationshipHookMode::Skip); + } + }); + + // SAFETY: The entities we're inserting will be the entities that were either already there or entities that we've just inserted. + existing_relations.clear(); + existing_relations.extend_from_iter(related.iter().copied()); + self.insert(R::RelationshipTarget::from_collection_risky( + existing_relations, + )); + + self + } + + /// Replaces all the related entities with a new set of entities. + /// + /// This is a more efficient of [`Self::replace_related`] which doesn't allocate. + /// The passed in arguments must adhere to these invariants: + /// - `entities_to_unrelate`: A slice of entities to remove from the relationship source. + /// Entities need not be related to this entity, but must not appear in `entities_to_relate` + /// - `entities_to_relate`: A slice of entities to relate to this entity. + /// This must contain all entities that will remain related (i.e. not those in `entities_to_unrelate`) plus the newly related entities. + /// - `newly_related_entities`: A subset of `entities_to_relate` containing only entities not already related to this entity. + /// - Slices **must not** contain any duplicates + /// + /// # Warning + /// + /// Violating these invariants may lead to panics, crashes or unpredictable engine behavior. + /// + /// # Panics + /// + /// Panics when debug assertions are enabled and any invariants are broken. + /// + // TODO: Consider making these iterators so users aren't required to allocate a separate buffers for the different slices. + pub fn replace_related_with_difference( + &mut self, + entities_to_unrelate: &[Entity], + entities_to_relate: &[Entity], + newly_related_entities: &[Entity], + ) -> &mut Self { + #[cfg(debug_assertions)] + { + let entities_to_relate = EntityHashSet::from_iter(entities_to_relate.iter().copied()); + let entities_to_unrelate = + EntityHashSet::from_iter(entities_to_unrelate.iter().copied()); + let mut newly_related_entities = + EntityHashSet::from_iter(newly_related_entities.iter().copied()); + assert!( + entities_to_relate.is_disjoint(&entities_to_unrelate), + "`entities_to_relate` ({entities_to_relate:?}) shared entities with `entities_to_unrelate` ({entities_to_unrelate:?})" + ); + assert!( + newly_related_entities.is_disjoint(&entities_to_unrelate), + "`newly_related_entities` ({newly_related_entities:?}) shared entities with `entities_to_unrelate ({entities_to_unrelate:?})`" + ); + assert!( + newly_related_entities.is_subset(&entities_to_relate), + "`newly_related_entities` ({newly_related_entities:?}) wasn't a subset of `entities_to_relate` ({entities_to_relate:?})" + ); + + if let Some(target) = self.get::() { + let existing_relationships: EntityHashSet = target.collection().iter().collect(); + + assert!( + existing_relationships.is_disjoint(&newly_related_entities), + "`newly_related_entities` contains an entity that wouldn't be newly related" + ); + + newly_related_entities.extend(existing_relationships); + newly_related_entities -= &entities_to_unrelate; + } + + assert_eq!(newly_related_entities, entities_to_relate, "`entities_to_relate` ({entities_to_relate:?}) didn't contain all entities that would end up related"); + }; + + if !self.contains::() { + self.add_related::(entities_to_relate); + + return self; + }; + + let this = self.id(); + self.world_scope(|world| { + for unrelate in entities_to_unrelate { + world.entity_mut(*unrelate).remove::(); + } + + for new_relation in newly_related_entities { + // We're changing the target collection manually so don't run the insert hook + world + .entity_mut(*new_relation) + .insert_with_relationship_hook_mode(R::from(this), RelationshipHookMode::Skip); + } + }); + + if !entities_to_relate.is_empty() { + if let Some(mut target) = self.get_mut::() { + // SAFETY: The invariants expected by this function mean we'll only be inserting entities that are already related. + let collection = target.collection_mut_risky(); + collection.clear(); + + collection.extend_from_iter(entities_to_relate.iter().copied()); + } else { + let mut empty = + ::Collection::with_capacity( + entities_to_relate.len(), + ); + empty.extend_from_iter(entities_to_relate.iter().copied()); + + // SAFETY: We've just initialized this collection and we know there's no `RelationshipTarget` on `self` + self.insert(R::RelationshipTarget::from_collection_risky(empty)); + } + } + + self + } + /// Relates the given entity to this with the relation `R`. /// /// See [`add_related`](Self::add_related) if you want to relate more than one entity. @@ -107,8 +353,15 @@ impl<'w> EntityWorldMut<'w> { } impl<'a> EntityCommands<'a> { + /// Spawns a entity related to this entity (with the `R` relationship) by taking a bundle + pub fn with_related(&mut self, bundle: impl Bundle) -> &mut Self { + let parent = self.id(); + self.commands.spawn((bundle, R::from(parent))); + self + } + /// Spawns entities related to this entity (with the `R` relationship) by taking a function that operates on a [`RelatedSpawner`]. - pub fn with_related( + pub fn with_related_entities( &mut self, func: impl FnOnce(&mut RelatedSpawnerCommands), ) -> &mut Self { @@ -121,14 +374,35 @@ impl<'a> EntityCommands<'a> { /// /// See [`add_one_related`](Self::add_one_related) if you want relate only one entity. pub fn add_related(&mut self, related: &[Entity]) -> &mut Self { - let id = self.id(); - let related = related.to_vec(); - self.commands().queue(move |world: &mut World| { - for related in related { - world.entity_mut(related).insert(R::from(id)); - } - }); - self + let related: Box<[Entity]> = related.into(); + + self.queue(move |mut entity: EntityWorldMut| { + entity.add_related::(&related); + }) + } + + /// Removes the relation `R` between this entity and all its related entities. + pub fn clear_related(&mut self) -> &mut Self { + self.queue(|mut entity: EntityWorldMut| { + entity.clear_related::(); + }) + } + + /// Relates the given entities to this entity with the relation `R`, starting at this particular index. + /// + /// If the `related` has duplicates, a related entity will take the index of its last occurrence in `related`. + /// If the indices go out of bounds, they will be clamped into bounds. + /// This will not re-order existing related entities unless they are in `related`. + pub fn insert_related(&mut self, index: usize, related: &[Entity]) -> &mut Self + where + ::Collection: + OrderedRelationshipSourceCollection, + { + let related: Box<[Entity]> = related.into(); + + self.queue(move |mut entity: EntityWorldMut| { + entity.insert_related::(index, &related); + }) } /// Relates the given entity to this with the relation `R`. @@ -138,14 +412,59 @@ impl<'a> EntityCommands<'a> { self.add_related::(&[entity]) } + /// Removes the relation `R` between this entity and the given entities. + pub fn remove_related(&mut self, related: &[Entity]) -> &mut Self { + let related: Box<[Entity]> = related.into(); + + self.queue(move |mut entity: EntityWorldMut| { + entity.remove_related::(&related); + }) + } + + /// Replaces all the related entities with the given set of new related entities. + pub fn replace_related(&mut self, related: &[Entity]) -> &mut Self { + let related: Box<[Entity]> = related.into(); + + self.queue(move |mut entity: EntityWorldMut| { + entity.replace_related::(&related); + }) + } + + /// Replaces all the related entities with a new set of entities. + /// + /// # Warning + /// + /// Failing to maintain the functions invariants may lead to erratic engine behavior including random crashes. + /// Refer to [`EntityWorldMut::replace_related_with_difference`] for a list of these invariants. + /// + /// # Panics + /// + /// Panics when debug assertions are enable, an invariant is are broken and the command is executed. + pub fn replace_related_with_difference( + &mut self, + entities_to_unrelate: &[Entity], + entities_to_relate: &[Entity], + newly_related_entities: &[Entity], + ) -> &mut Self { + let entities_to_unrelate: Box<[Entity]> = entities_to_unrelate.into(); + let entities_to_relate: Box<[Entity]> = entities_to_relate.into(); + let newly_related_entities: Box<[Entity]> = newly_related_entities.into(); + + self.queue(move |mut entity: EntityWorldMut| { + entity.replace_related_with_difference::( + &entities_to_unrelate, + &entities_to_relate, + &newly_related_entities, + ); + }) + } + /// Despawns entities that relate to this one via the given [`RelationshipTarget`]. /// This entity will not be despawned. pub fn despawn_related(&mut self) -> &mut Self { - let id = self.id(); - self.commands.queue(move |world: &mut World| { - world.entity_mut(id).despawn_related::(); - }); - self + self.queue(move |mut entity: EntityWorldMut| { + entity.despawn_related::(); + }) } /// Inserts a component or bundle of components into the entity and all related entities, @@ -159,11 +478,9 @@ impl<'a> EntityCommands<'a> { &mut self, bundle: impl Bundle + Clone, ) -> &mut Self { - let id = self.id(); - self.commands.queue(move |world: &mut World| { - world.entity_mut(id).insert_recursive::(bundle); - }); - self + self.queue(move |mut entity: EntityWorldMut| { + entity.insert_recursive::(bundle); + }) } /// Removes a component or bundle of components of type `B` from the entity and all related entities, @@ -174,11 +491,9 @@ impl<'a> EntityCommands<'a> { /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. pub fn remove_recursive(&mut self) -> &mut Self { - let id = self.id(); - self.commands.queue(move |world: &mut World| { - world.entity_mut(id).remove_recursive::(); - }); - self + self.queue(move |mut entity: EntityWorldMut| { + entity.remove_recursive::(); + }) } } @@ -216,6 +531,16 @@ impl<'w, R: Relationship> RelatedSpawner<'w, R> { pub fn target_entity(&self) -> Entity { self.target } + + /// Returns a reference to the underlying [`World`]. + pub fn world(&self) -> &World { + self.world + } + + /// Returns a mutable reference to the underlying [`World`]. + pub fn world_mut(&mut self) -> &mut World { + self.world + } } /// Uses commands to spawn related "source" entities with the given [`Relationship`], targeting @@ -277,9 +602,9 @@ mod tests { let mut world = World::new(); let a = world.spawn_empty().id(); - let b = world.spawn(ChildOf { parent: a }).id(); - let c = world.spawn(ChildOf { parent: a }).id(); - let d = world.spawn(ChildOf { parent: b }).id(); + let b = world.spawn(ChildOf(a)).id(); + let c = world.spawn(ChildOf(a)).id(); + let d = world.spawn(ChildOf(b)).id(); world .entity_mut(a) @@ -310,4 +635,19 @@ mod tests { assert!(!world.entity(entity).contains::()); } } + + #[test] + fn remove_all_related() { + let mut world = World::new(); + + let a = world.spawn_empty().id(); + let b = world.spawn(ChildOf(a)).id(); + let c = world.spawn(ChildOf(a)).id(); + + world.entity_mut(a).clear_related::(); + + assert_eq!(world.entity(a).get::(), None); + assert_eq!(world.entity(b).get::(), None); + assert_eq!(world.entity(c).get::(), None); + } } diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index c3748e46a7..d4ea45f64f 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -1,5 +1,12 @@ -use crate::entity::{hash_set::EntityHashSet, Entity}; +use alloc::collections::{btree_set, BTreeSet}; +use core::{ + hash::BuildHasher, + ops::{Deref, DerefMut}, +}; + +use crate::entity::{Entity, EntityHashSet, EntityIndexSet}; use alloc::vec::Vec; +use indexmap::IndexSet; use smallvec::SmallVec; /// The internal [`Entity`] collection used by a [`RelationshipTarget`](crate::relationship::RelationshipTarget) component. @@ -16,9 +23,19 @@ pub trait RelationshipSourceCollection { where Self: 'a; + /// Creates a new empty instance. + fn new() -> Self; + /// Returns an instance with the given pre-allocated entity `capacity`. + /// + /// Some collections will ignore the provided `capacity` and return a default instance. fn with_capacity(capacity: usize) -> Self; + /// Reserves capacity for at least `additional` more entities to be inserted. + /// + /// Not all collections support this operation, in which case it is a no-op. + fn reserve(&mut self, additional: usize); + /// Adds the given `entity` to the collection. /// /// Returns whether the entity was added to the collection. @@ -41,16 +58,92 @@ pub trait RelationshipSourceCollection { /// Clears the collection. fn clear(&mut self); + /// Attempts to save memory by shrinking the capacity to fit the current length. + /// + /// This operation is a no-op for collections that do not support it. + fn shrink_to_fit(&mut self); + /// Returns true if the collection contains no entities. #[inline] fn is_empty(&self) -> bool { self.len() == 0 } + + /// Add multiple entities to collection at once. + /// + /// May be faster than repeatedly calling [`Self::add`]. + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + // The method name shouldn't conflict with `Extend::extend` as it's in the rust prelude and + // would always conflict with it. + for entity in entities { + self.add(entity); + } + } +} + +/// This trait signals that a [`RelationshipSourceCollection`] is ordered. +pub trait OrderedRelationshipSourceCollection: RelationshipSourceCollection { + /// Inserts the entity at a specific index. + /// If the index is too large, the entity will be added to the end of the collection. + fn insert(&mut self, index: usize, entity: Entity); + /// Removes the entity at the specified idnex if it exists. + fn remove_at(&mut self, index: usize) -> Option; + /// Inserts the entity at a specific index. + /// This will never reorder other entities. + /// If the index is too large, the entity will be added to the end of the collection. + fn insert_stable(&mut self, index: usize, entity: Entity); + /// Removes the entity at the specified idnex if it exists. + /// This will never reorder other entities. + fn remove_at_stable(&mut self, index: usize) -> Option; + /// Sorts the source collection. + fn sort(&mut self); + /// Inserts the entity at the proper place to maintain sorting. + fn insert_sorted(&mut self, entity: Entity); + + /// This places the most recently added entity at the particular index. + fn place_most_recent(&mut self, index: usize); + + /// This places the given entity at the particular index. + /// This will do nothing if the entity is not in the collection. + /// If the index is out of bounds, this will put the entity at the end. + fn place(&mut self, entity: Entity, index: usize); + + /// Adds the entity at index 0. + fn push_front(&mut self, entity: Entity) { + self.insert(0, entity); + } + + /// Adds the entity to the back of the collection. + fn push_back(&mut self, entity: Entity) { + self.insert(usize::MAX, entity); + } + + /// Removes the first entity. + fn pop_front(&mut self) -> Option { + self.remove_at(0) + } + + /// Removes the last entity. + fn pop_back(&mut self) -> Option { + if self.is_empty() { + None + } else { + self.remove_at(self.len() - 1) + } + } } impl RelationshipSourceCollection for Vec { type SourceIter<'a> = core::iter::Copied>; + fn new() -> Self { + Vec::new() + } + + fn reserve(&mut self, additional: usize) { + Vec::reserve(self, additional); + } + fn with_capacity(capacity: usize) -> Self { Vec::with_capacity(capacity) } @@ -64,7 +157,6 @@ impl RelationshipSourceCollection for Vec { fn remove(&mut self, entity: Entity) -> bool { if let Some(index) = <[Entity]>::iter(self).position(|e| *e == entity) { Vec::remove(self, index); - return true; } @@ -82,11 +174,77 @@ impl RelationshipSourceCollection for Vec { fn clear(&mut self) { self.clear(); } + + fn shrink_to_fit(&mut self) { + Vec::shrink_to_fit(self); + } + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + self.extend(entities); + } +} + +impl OrderedRelationshipSourceCollection for Vec { + fn insert(&mut self, index: usize, entity: Entity) { + self.push(entity); + let len = self.len(); + if index < len { + self.swap(index, len - 1); + } + } + + fn remove_at(&mut self, index: usize) -> Option { + (index < self.len()).then(|| self.swap_remove(index)) + } + + fn insert_stable(&mut self, index: usize, entity: Entity) { + if index < self.len() { + Vec::insert(self, index, entity); + } else { + self.push(entity); + } + } + + fn remove_at_stable(&mut self, index: usize) -> Option { + (index < self.len()).then(|| self.remove(index)) + } + + fn sort(&mut self) { + self.sort_unstable(); + } + + fn insert_sorted(&mut self, entity: Entity) { + let index = self.partition_point(|e| e <= &entity); + self.insert_stable(index, entity); + } + + fn place_most_recent(&mut self, index: usize) { + if let Some(entity) = self.pop() { + let index = index.min(self.len()); + self.insert(index, entity); + } + } + + fn place(&mut self, entity: Entity, index: usize) { + if let Some(current) = <[Entity]>::iter(self).position(|e| *e == entity) { + let index = index.min(self.len()); + Vec::remove(self, current); + self.insert(index, entity); + }; + } } impl RelationshipSourceCollection for EntityHashSet { type SourceIter<'a> = core::iter::Copied>; + fn new() -> Self { + EntityHashSet::new() + } + + fn reserve(&mut self, additional: usize) { + self.0.reserve(additional); + } + fn with_capacity(capacity: usize) -> Self { EntityHashSet::with_capacity(capacity) } @@ -112,11 +270,27 @@ impl RelationshipSourceCollection for EntityHashSet { fn clear(&mut self) { self.0.clear(); } + + fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit(); + } + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + self.extend(entities); + } } impl RelationshipSourceCollection for SmallVec<[Entity; N]> { type SourceIter<'a> = core::iter::Copied>; + fn new() -> Self { + SmallVec::new() + } + + fn reserve(&mut self, additional: usize) { + SmallVec::reserve(self, additional); + } + fn with_capacity(capacity: usize) -> Self { SmallVec::with_capacity(capacity) } @@ -130,7 +304,6 @@ impl RelationshipSourceCollection for SmallVec<[Entity; N]> { fn remove(&mut self, entity: Entity) -> bool { if let Some(index) = <[Entity]>::iter(self).position(|e| *e == entity) { SmallVec::remove(self, index); - return true; } @@ -148,16 +321,36 @@ impl RelationshipSourceCollection for SmallVec<[Entity; N]> { fn clear(&mut self) { self.clear(); } + + fn shrink_to_fit(&mut self) { + SmallVec::shrink_to_fit(self); + } + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + self.extend(entities); + } } impl RelationshipSourceCollection for Entity { - type SourceIter<'a> = core::iter::Once; + type SourceIter<'a> = core::option::IntoIter; - fn with_capacity(_capacity: usize) -> Self { + fn new() -> Self { Entity::PLACEHOLDER } + fn reserve(&mut self, _: usize) {} + + fn with_capacity(_capacity: usize) -> Self { + Self::new() + } + fn add(&mut self, entity: Entity) -> bool { + assert_eq!( + *self, + Entity::PLACEHOLDER, + "Entity {entity} attempted to target an entity with a one-to-one relationship, but it is already targeted by {}. You must remove the original relationship first.", + *self + ); *self = entity; true @@ -174,7 +367,11 @@ impl RelationshipSourceCollection for Entity { } fn iter(&self) -> Self::SourceIter<'_> { - core::iter::once(*self) + if *self == Entity::PLACEHOLDER { + None.into_iter() + } else { + Some(*self).into_iter() + } } fn len(&self) -> usize { @@ -187,6 +384,203 @@ impl RelationshipSourceCollection for Entity { fn clear(&mut self) { *self = Entity::PLACEHOLDER; } + + fn shrink_to_fit(&mut self) {} + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + for entity in entities { + assert_eq!( + *self, + Entity::PLACEHOLDER, + "Entity {entity} attempted to target an entity with a one-to-one relationship, but it is already targeted by {}. You must remove the original relationship first.", + *self + ); + *self = entity; + } + } +} + +impl OrderedRelationshipSourceCollection for SmallVec<[Entity; N]> { + fn insert(&mut self, index: usize, entity: Entity) { + self.push(entity); + let len = self.len(); + if index < len { + self.swap(index, len - 1); + } + } + + fn remove_at(&mut self, index: usize) -> Option { + (index < self.len()).then(|| self.swap_remove(index)) + } + + fn insert_stable(&mut self, index: usize, entity: Entity) { + if index < self.len() { + SmallVec::<[Entity; N]>::insert(self, index, entity); + } else { + self.push(entity); + } + } + + fn remove_at_stable(&mut self, index: usize) -> Option { + (index < self.len()).then(|| self.remove(index)) + } + + fn sort(&mut self) { + self.sort_unstable(); + } + + fn insert_sorted(&mut self, entity: Entity) { + let index = self.partition_point(|e| e <= &entity); + self.insert_stable(index, entity); + } + + fn place_most_recent(&mut self, index: usize) { + if let Some(entity) = self.pop() { + let index = index.min(self.len() - 1); + self.insert(index, entity); + } + } + + fn place(&mut self, entity: Entity, index: usize) { + if let Some(current) = <[Entity]>::iter(self).position(|e| *e == entity) { + // The len is at least 1, so the subtraction is safe. + let index = index.min(self.len() - 1); + SmallVec::<[Entity; N]>::remove(self, current); + self.insert(index, entity); + }; + } +} + +impl RelationshipSourceCollection for IndexSet { + type SourceIter<'a> + = core::iter::Copied> + where + S: 'a; + + fn new() -> Self { + IndexSet::default() + } + + fn reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + fn with_capacity(capacity: usize) -> Self { + IndexSet::with_capacity_and_hasher(capacity, S::default()) + } + + fn add(&mut self, entity: Entity) -> bool { + self.insert(entity) + } + + fn remove(&mut self, entity: Entity) -> bool { + self.shift_remove(&entity) + } + + fn iter(&self) -> Self::SourceIter<'_> { + self.iter().copied() + } + + fn len(&self) -> usize { + self.len() + } + + fn clear(&mut self) { + self.clear(); + } + + fn shrink_to_fit(&mut self) { + self.shrink_to_fit(); + } + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + self.extend(entities); + } +} + +impl RelationshipSourceCollection for EntityIndexSet { + type SourceIter<'a> = core::iter::Copied>; + + fn new() -> Self { + EntityIndexSet::new() + } + + fn reserve(&mut self, additional: usize) { + self.deref_mut().reserve(additional); + } + + fn with_capacity(capacity: usize) -> Self { + EntityIndexSet::with_capacity(capacity) + } + + fn add(&mut self, entity: Entity) -> bool { + self.insert(entity) + } + + fn remove(&mut self, entity: Entity) -> bool { + self.deref_mut().shift_remove(&entity) + } + + fn iter(&self) -> Self::SourceIter<'_> { + self.iter().copied() + } + + fn len(&self) -> usize { + self.deref().len() + } + + fn clear(&mut self) { + self.deref_mut().clear(); + } + + fn shrink_to_fit(&mut self) { + self.deref_mut().shrink_to_fit(); + } + + fn extend_from_iter(&mut self, entities: impl IntoIterator) { + self.extend(entities); + } +} + +impl RelationshipSourceCollection for BTreeSet { + type SourceIter<'a> = core::iter::Copied>; + + fn new() -> Self { + BTreeSet::new() + } + + fn with_capacity(_: usize) -> Self { + // BTreeSet doesn't have a capacity + Self::new() + } + + fn reserve(&mut self, _: usize) { + // BTreeSet doesn't have a capacity + } + + fn add(&mut self, entity: Entity) -> bool { + self.insert(entity) + } + + fn remove(&mut self, entity: Entity) -> bool { + self.remove(&entity) + } + + fn iter(&self) -> Self::SourceIter<'_> { + self.iter().copied() + } + + fn len(&self) -> usize { + self.len() + } + + fn clear(&mut self) { + self.clear(); + } + + fn shrink_to_fit(&mut self) { + // BTreeSet doesn't have a capacity + } } #[cfg(test)] @@ -290,4 +684,75 @@ mod tests { assert!(world.get::(b).is_none()); assert_eq!(a, world.get::(c).unwrap().0); } + + #[test] + fn entity_index_map() { + #[derive(Component)] + #[relationship(relationship_target = RelTarget)] + struct Rel(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Rel, linked_spawn)] + struct RelTarget(EntityHashSet); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn_empty().id(); + + let d = world.spawn_empty().id(); + + world.entity_mut(a).add_related::(&[b, c, d]); + + let rel_target = world.get::(a).unwrap(); + let collection = rel_target.collection(); + + // Insertions should maintain ordering + assert!(collection.iter().eq(&[d, c, b])); + + world.entity_mut(c).despawn(); + + let rel_target = world.get::(a).unwrap(); + let collection = rel_target.collection(); + + // Removals should maintain ordering + assert!(collection.iter().eq(&[d, b])); + } + + #[test] + #[should_panic] + fn one_to_one_relationship_shared_target() { + #[derive(Component)] + #[relationship(relationship_target = Below)] + struct Above(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Above)] + struct Below(Entity); + let mut world = World::new(); + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn_empty().id(); + + world.entity_mut(a).insert(Above(c)); + world.entity_mut(b).insert(Above(c)); + } + + #[test] + fn one_to_one_relationship_reinsert() { + #[derive(Component)] + #[relationship(relationship_target = Below)] + struct Above(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Above)] + struct Below(Entity); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + + world.entity_mut(a).insert(Above(b)); + world.entity_mut(a).insert(Above(b)); + } } diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index c3f7805631..7da4f31113 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -54,7 +54,7 @@ pub use bevy_ecs_macros::Resource; /// ``` /// # use std::cell::RefCell; /// # use bevy_ecs::resource::Resource; -/// use bevy_utils::synccell::SyncCell; +/// use bevy_platform::cell::SyncCell; /// /// #[derive(Resource)] /// struct ActuallySync { @@ -66,7 +66,7 @@ pub use bevy_ecs_macros::Resource; /// [`World`]: crate::world::World /// [`Res`]: crate::system::Res /// [`ResMut`]: crate::system::ResMut -/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`SyncCell`]: bevy_platform::cell::SyncCell #[diagnostic::on_unimplemented( message = "`{Self}` is not a `Resource`", label = "invalid `Resource`", diff --git a/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs b/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs index 171abee964..dda6d604a7 100644 --- a/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs +++ b/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs @@ -1,6 +1,6 @@ use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use crate::system::IntoSystem; use crate::world::World; diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index a85a8c6fa4..2b31ad50c7 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -16,16 +16,16 @@ pub type BoxedCondition = Box>; /// /// # Marker type parameter /// -/// `Condition` trait has `Marker` type parameter, which has no special meaning, +/// `SystemCondition` trait has `Marker` type parameter, which has no special meaning, /// but exists to work around the limitation of Rust's trait system. /// /// Type parameter in return type can be set to `<()>` by calling [`IntoSystem::into_system`], /// but usually have to be specified when passing a condition to a function. /// /// ``` -/// # use bevy_ecs::schedule::Condition; +/// # use bevy_ecs::schedule::SystemCondition; /// # use bevy_ecs::system::IntoSystem; -/// fn not_condition(a: impl Condition) -> impl Condition<()> { +/// fn not_condition(a: impl SystemCondition) -> impl SystemCondition<()> { /// IntoSystem::into_system(a.map(|x| !x)) /// } /// ``` @@ -34,7 +34,7 @@ pub type BoxedCondition = Box>; /// A condition that returns true every other time it's called. /// ``` /// # use bevy_ecs::prelude::*; -/// fn every_other_time() -> impl Condition<()> { +/// fn every_other_time() -> impl SystemCondition<()> { /// IntoSystem::into_system(|mut flag: Local| { /// *flag = !*flag; /// *flag @@ -58,7 +58,7 @@ pub type BoxedCondition = Box>; /// /// ``` /// # use bevy_ecs::prelude::*; -/// fn identity() -> impl Condition<(), In> { +/// fn identity() -> impl SystemCondition<(), In> { /// IntoSystem::into_system(|In(x)| x) /// } /// @@ -71,7 +71,9 @@ pub type BoxedCondition = Box>; /// # world.insert_resource(DidRun(false)); /// # app.run(&mut world); /// # assert!(world.resource::().0); -pub trait Condition: sealed::Condition { +pub trait SystemCondition: + sealed::SystemCondition +{ /// Returns a new run condition that only returns `true` /// if both this one and the passed `and` return `true`. /// @@ -116,7 +118,7 @@ pub trait Condition: sealed::Condition /// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`]. /// /// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals - fn and>(self, and: C) -> And { + fn and>(self, and: C) -> And { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(and); let name = format!("{} && {}", a.name(), b.name()); @@ -168,7 +170,7 @@ pub trait Condition: sealed::Condition /// ), /// ); /// ``` - fn nand>(self, nand: C) -> Nand { + fn nand>(self, nand: C) -> Nand { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(nand); let name = format!("!({} && {})", a.name(), b.name()); @@ -220,7 +222,7 @@ pub trait Condition: sealed::Condition /// ), /// ); /// ``` - fn nor>(self, nor: C) -> Nor { + fn nor>(self, nor: C) -> Nor { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(nor); let name = format!("!({} || {})", a.name(), b.name()); @@ -267,7 +269,7 @@ pub trait Condition: sealed::Condition /// # app.run(&mut world); /// # assert!(world.resource::().0); /// ``` - fn or>(self, or: C) -> Or { + fn or>(self, or: C) -> Or { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(or); let name = format!("{} || {}", a.name(), b.name()); @@ -319,7 +321,7 @@ pub trait Condition: sealed::Condition /// ), /// ); /// ``` - fn xnor>(self, xnor: C) -> Xnor { + fn xnor>(self, xnor: C) -> Xnor { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(xnor); let name = format!("!({} ^ {})", a.name(), b.name()); @@ -361,7 +363,7 @@ pub trait Condition: sealed::Condition /// ); /// # app.run(&mut world); /// ``` - fn xor>(self, xor: C) -> Xor { + fn xor>(self, xor: C) -> Xor { let a = IntoSystem::into_system(self); let b = IntoSystem::into_system(xor); let name = format!("({} ^ {})", a.name(), b.name()); @@ -369,12 +371,15 @@ pub trait Condition: sealed::Condition } } -impl Condition for F where F: sealed::Condition {} +impl SystemCondition for F where + F: sealed::SystemCondition +{ +} mod sealed { use crate::system::{IntoSystem, ReadOnlySystem, SystemInput}; - pub trait Condition: + pub trait SystemCondition: IntoSystem { // This associated type is necessary to let the compiler @@ -382,7 +387,7 @@ mod sealed { type ReadOnlySystem: ReadOnlySystem; } - impl Condition for F + impl SystemCondition for F where F: IntoSystem, F::System: ReadOnlySystem, @@ -391,9 +396,9 @@ mod sealed { } } -/// A collection of [run conditions](Condition) that may be useful in any bevy app. +/// A collection of [run conditions](SystemCondition) that may be useful in any bevy app. pub mod common_conditions { - use super::{Condition, NotSystem}; + use super::{NotSystem, SystemCondition}; use crate::{ change_detection::DetectChanges, event::{Event, EventReader}, @@ -405,7 +410,7 @@ pub mod common_conditions { }; use alloc::format; - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// on the first time the condition is run and false every time after. /// /// # Example @@ -443,7 +448,7 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if the resource exists. /// /// # Example @@ -478,7 +483,7 @@ pub mod common_conditions { res.is_some() } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// Generates a [`SystemCondition`]-satisfying closure that returns `true` /// if the resource is equal to `value`. /// /// # Panics @@ -518,7 +523,7 @@ pub mod common_conditions { move |res: Res| *res == value } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// Generates a [`SystemCondition`]-satisfying closure that returns `true` /// if the resource exists and is equal to `value`. /// /// The condition will return `false` if the resource does not exist. @@ -563,7 +568,7 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if the resource of the given type has been added since the condition was last checked. /// /// # Example @@ -604,13 +609,12 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` - /// if the resource of the given type has had its value changed since the condition - /// was last checked. + /// A [`SystemCondition`]-satisfying system that returns `true` + /// if the resource of the given type has been added or mutably dereferenced + /// since the condition was last checked. /// - /// The value is considered changed when it is added. The first time this condition - /// is checked after the resource was added, it will return `true`. - /// Change detection behaves like this everywhere in Bevy. + /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). + /// Bevy does not compare resources to their previous values. /// /// # Panics /// @@ -658,15 +662,12 @@ pub mod common_conditions { res.is_changed() } - /// A [`Condition`]-satisfying system that returns `true` - /// if the resource of the given type has had its value changed since the condition + /// A [`SystemCondition`]-satisfying system that returns `true` + /// if the resource of the given type has been added or mutably dereferenced since the condition /// was last checked. /// - /// The value is considered changed when it is added. The first time this condition - /// is checked after the resource was added, it will return `true`. - /// Change detection behaves like this everywhere in Bevy. - /// - /// This run condition does not detect when the resource is removed. + /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). + /// Bevy does not compare resources to their previous values. /// /// The condition will return `false` if the resource does not exist. /// @@ -718,16 +719,12 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` - /// if the resource of the given type has had its value changed since the condition + /// A [`SystemCondition`]-satisfying system that returns `true` + /// if the resource of the given type has been added, removed or mutably dereferenced since the condition /// was last checked. /// - /// The value is considered changed when it is added. The first time this condition - /// is checked after the resource was added, it will return `true`. - /// Change detection behaves like this everywhere in Bevy. - /// - /// This run condition also detects removal. It will return `true` if the resource - /// has been removed since the run condition was last checked. + /// **Note** that simply *mutably dereferencing* a resource is considered a change ([`DerefMut`](std::ops::DerefMut)). + /// Bevy does not compare resources to their previous values. /// /// The condition will return `false` if the resource does not exist. /// @@ -795,7 +792,7 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if the resource of the given type has been removed since the condition was last checked. /// /// # Example @@ -847,7 +844,7 @@ pub mod common_conditions { } } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if there are any new events of the given type since it was last called. /// /// # Example @@ -891,7 +888,7 @@ pub mod common_conditions { reader.read().count() > 0 } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if there are any entities with the given component type. /// /// # Example @@ -928,7 +925,7 @@ pub mod common_conditions { !query.is_empty() } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if there are any entity with a component of the given type removed. pub fn any_component_removed(mut removals: RemovedComponents) -> bool { // `RemovedComponents` based on events and therefore events need to be consumed, @@ -939,13 +936,13 @@ pub mod common_conditions { removals.read().count() > 0 } - /// A [`Condition`]-satisfying system that returns `true` + /// A [`SystemCondition`]-satisfying system that returns `true` /// if there are any entities that match the given [`QueryFilter`]. pub fn any_match_filter(query: Query<(), F>) -> bool { !query.is_empty() } - /// Generates a [`Condition`] that inverses the result of passed one. + /// Generates a [`SystemCondition`] that inverses the result of passed one. /// /// # Example /// @@ -984,7 +981,7 @@ pub mod common_conditions { NotSystem::new(super::NotMarker, condition, name.into()) } - /// Generates a [`Condition`] that returns true when the passed one changes. + /// Generates a [`SystemCondition`] that returns true when the passed one changes. /// /// The first time this is called, the passed condition is assumed to have been previously false. /// @@ -1022,10 +1019,10 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn condition_changed(condition: C) -> impl Condition<(), CIn> + pub fn condition_changed(condition: C) -> impl SystemCondition<(), CIn> where CIn: SystemInput, - C: Condition, + C: SystemCondition, { IntoSystem::into_system(condition.pipe(|In(new): In, mut prev: Local| { let changed = *prev != new; @@ -1034,7 +1031,7 @@ pub mod common_conditions { })) } - /// Generates a [`Condition`] that returns true when the result of + /// Generates a [`SystemCondition`] that returns true when the result of /// the passed one went from false to true since the last time this was called. /// /// The first time this is called, the passed condition is assumed to have been previously false. @@ -1078,10 +1075,13 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 2); /// ``` - pub fn condition_changed_to(to: bool, condition: C) -> impl Condition<(), CIn> + pub fn condition_changed_to( + to: bool, + condition: C, + ) -> impl SystemCondition<(), CIn> where CIn: SystemInput, - C: Condition, + C: SystemCondition, { IntoSystem::into_system(condition.pipe( move |In(new): In, mut prev: Local| -> bool { @@ -1262,7 +1262,7 @@ where #[cfg(test)] mod tests { - use super::{common_conditions::*, Condition}; + use super::{common_conditions::*, SystemCondition}; use crate::query::With; use crate::{ change_detection::ResMut, diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 8188dc144c..f1a48e432b 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -3,9 +3,10 @@ use variadics_please::all_tuples; use crate::{ error::Result, + never::Never, schedule::{ auto_insert_apply_deferred::IgnoreDeferred, - condition::{BoxedCondition, Condition}, + condition::{BoxedCondition, SystemCondition}, graph::{Ambiguity, Dependency, DependencyKind, GraphInfo}, set::{InternedSystemSet, IntoSystemSet, SystemSet}, Chain, @@ -13,11 +14,11 @@ use crate::{ system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System}, }; -fn new_condition(condition: impl Condition) -> BoxedCondition { +fn new_condition(condition: impl SystemCondition) -> BoxedCondition { let condition_system = IntoSystem::into_system(condition); assert!( condition_system.is_send(), - "Condition `{}` accesses `NonSend` resources. This is not currently supported.", + "SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.", condition_system.name() ); @@ -190,7 +191,7 @@ impl> ScheduleConfig } } - fn distributive_run_if_inner(&mut self, condition: impl Condition + Clone) { + fn distributive_run_if_inner(&mut self, condition: impl SystemCondition + Clone) { match self { Self::ScheduleConfig(config) => { config.conditions.push(new_condition(condition)); @@ -381,8 +382,8 @@ pub trait IntoScheduleConfigs(self, condition: impl Condition + Clone) -> ScheduleConfigs { + fn distributive_run_if( + self, + condition: impl SystemCondition + Clone, + ) -> ScheduleConfigs { self.into_configs().distributive_run_if(condition) } - /// Run the systems only if the [`Condition`] is `true`. + /// Run the systems only if the [`SystemCondition`] is `true`. /// - /// The `Condition` will be evaluated at most once (per schedule run), + /// The `SystemCondition` will be evaluated at most once (per schedule run), /// the first time a system in this set prepares to run. /// /// If this set contains more than one system, calling `run_if` is equivalent to adding each @@ -443,7 +447,7 @@ pub trait IntoScheduleConfigs(self, condition: impl Condition) -> ScheduleConfigs { + fn run_if(self, condition: impl SystemCondition) -> ScheduleConfigs { self.into_configs().run_if(condition) } @@ -525,13 +529,13 @@ impl> IntoScheduleCo fn distributive_run_if( mut self, - condition: impl Condition + Clone, + condition: impl SystemCondition + Clone, ) -> ScheduleConfigs { self.distributive_run_if_inner(condition); self } - fn run_if(mut self, condition: impl Condition) -> ScheduleConfigs { + fn run_if(mut self, condition: impl SystemCondition) -> ScheduleConfigs { self.run_if_dyn(new_condition(condition)); self } @@ -570,6 +574,16 @@ where } } +impl IntoScheduleConfigs for F +where + F: IntoSystem<(), Never, Marker>, +{ + fn into_configs(self) -> ScheduleConfigs { + let wrapper = InfallibleSystemWrapper::new(IntoSystem::into_system(self)); + ScheduleConfigs::ScheduleConfig(ScheduleSystem::into_config(Box::new(wrapper))) + } +} + /// Marker component to allow for conflicting implementations of [`IntoScheduleConfigs`] #[doc(hidden)] pub struct Fallible; diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 767ae1cd99..ef7c639038 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -6,6 +6,7 @@ mod single_threaded; use alloc::{borrow::Cow, vec, vec::Vec}; use core::any::TypeId; +#[expect(deprecated, reason = "We still need to support this.")] pub use self::{simple::SimpleExecutor, single_threaded::SingleThreadedExecutor}; #[cfg(feature = "std")] @@ -14,13 +15,12 @@ pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor}; use fixedbitset::FixedBitSet; use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, - error::{BevyError, Result, SystemErrorContext}, + error::{BevyError, ErrorContext, Result}, prelude::{IntoSystemSet, SystemSet}, - query::Access, + query::{Access, FilteredAccessSet}, schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet}, - system::{ScheduleSystem, System, SystemIn}, + system::{ScheduleSystem, System, SystemIn, SystemParamValidationError}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, }; @@ -33,7 +33,7 @@ pub(super) trait SystemExecutor: Send + Sync { schedule: &mut SystemSchedule, world: &mut World, skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: fn(BevyError, ErrorContext), ); fn set_apply_final_deferred(&mut self, value: bool); } @@ -53,6 +53,10 @@ pub enum ExecutorKind { SingleThreaded, /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred) /// immediately after running each system. + #[deprecated( + since = "0.17.0", + note = "Use SingleThreaded instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." + )] Simple, /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. #[cfg(feature = "std")] @@ -118,17 +122,6 @@ impl SystemSchedule { } } -/// See [`ApplyDeferred`]. -#[deprecated( - since = "0.16.0", - note = "Use `ApplyDeferred` instead. This was previously a function but is now a marker struct System." -)] -#[expect( - non_upper_case_globals, - reason = "This item is deprecated; as such, its previous name needs to stay." -)] -pub const apply_deferred: ApplyDeferred = ApplyDeferred; - /// A special [`System`] that instructs the executor to call /// [`System::apply_deferred`] on the systems that have run but not applied /// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers. @@ -174,9 +167,8 @@ impl System for ApplyDeferred { const { &Access::new() } } - fn archetype_component_access(&self) -> &Access { - // This system accesses no archetype components. - const { &Access::new() } + fn component_access_set(&self) -> &FilteredAccessSet { + const { &FilteredAccessSet::new() } } fn is_send(&self) -> bool { @@ -221,16 +213,17 @@ impl System for ApplyDeferred { fn queue_deferred(&mut self, _world: DeferredWorld) {} - unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + _world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { // This system is always valid to run because it doesn't do anything, // and only used as a marker for the executor. - true + Ok(()) } fn initialize(&mut self, _world: &mut World) {} - fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {} - fn check_change_tick(&mut self, _change_tick: Tick) {} fn default_system_sets(&self) -> Vec { @@ -264,38 +257,54 @@ impl IntoSystemSet<()> for ApplyDeferred { mod __rust_begin_short_backtrace { use core::hint::black_box; + #[cfg(feature = "std")] + use crate::world::unsafe_world_cell::UnsafeWorldCell; use crate::{ error::Result, system::{ReadOnlySystem, ScheduleSystem}, - world::{unsafe_world_cell::UnsafeWorldCell, World}, + world::World, }; /// # Safety /// See `System::run_unsafe`. + // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`. + #[cfg(feature = "std")] #[inline(never)] pub(super) unsafe fn run_unsafe(system: &mut ScheduleSystem, world: UnsafeWorldCell) -> Result { let result = system.run_unsafe((), world); + // Call `black_box` to prevent this frame from being tail-call optimized away black_box(()); result } /// # Safety /// See `ReadOnlySystem::run_unsafe`. - #[cfg_attr( - not(feature = "std"), - expect(dead_code, reason = "currently only used with the std feature") - )] + // This is only used by `MultiThreadedExecutor`, and would be dead code without `std`. + #[cfg(feature = "std")] #[inline(never)] pub(super) unsafe fn readonly_run_unsafe( system: &mut dyn ReadOnlySystem, world: UnsafeWorldCell, ) -> O { + // Call `black_box` to prevent this frame from being tail-call optimized away black_box(system.run_unsafe((), world)) } #[inline(never)] pub(super) fn run(system: &mut ScheduleSystem, world: &mut World) -> Result { let result = system.run((), world); + // Call `black_box` to prevent this frame from being tail-call optimized away + black_box(()); + result + } + + #[inline(never)] + pub(super) fn run_without_applying_deferred( + system: &mut ScheduleSystem, + world: &mut World, + ) -> Result { + let result = system.run_without_applying_deferred((), world); + // Call `black_box` to prevent this frame from being tail-call optimized away black_box(()); result } @@ -305,6 +314,7 @@ mod __rust_begin_short_backtrace { system: &mut dyn ReadOnlySystem, world: &mut World, ) -> O { + // Call `black_box` to prevent this frame from being tail-call optimized away black_box(system.run((), world)) } } @@ -312,81 +322,260 @@ mod __rust_begin_short_backtrace { #[cfg(test)] mod tests { use crate::{ - prelude::{IntoScheduleConfigs, Resource, Schedule, SystemSet}, + prelude::{Component, In, IntoSystem, Resource, Schedule}, schedule::ExecutorKind, - system::{Commands, Res, WithParamWarnPolicy}, + system::{Populated, Res, ResMut, Single}, world::World, }; - #[derive(Resource)] - struct R1; - - #[derive(Resource)] - struct R2; + #[derive(Component)] + struct TestComponent; const EXECUTORS: [ExecutorKind; 3] = [ + #[expect(deprecated, reason = "We still need to test this.")] ExecutorKind::Simple, ExecutorKind::SingleThreaded, ExecutorKind::MultiThreaded, ]; + #[derive(Resource, Default)] + struct TestState { + populated_ran: bool, + single_ran: bool, + } + + #[derive(Resource, Default)] + struct Counter(u8); + + fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut) { + state.single_ran = true; + } + + fn set_populated_state( + mut _populated: Populated<&TestComponent>, + mut state: ResMut, + ) { + state.populated_ran = true; + } + #[test] - fn invalid_system_param_skips() { + #[expect(clippy::print_stdout, reason = "std and println are allowed in tests")] + fn single_and_populated_skipped_and_run() { for executor in EXECUTORS { - invalid_system_param_skips_core(executor); + std::println!("Testing executor: {executor:?}"); + + let mut world = World::new(); + world.init_resource::(); + + let mut schedule = Schedule::default(); + schedule.set_executor_kind(executor); + schedule.add_systems((set_single_state, set_populated_state)); + schedule.run(&mut world); + + let state = world.get_resource::().unwrap(); + assert!(!state.single_ran); + assert!(!state.populated_ran); + + world.spawn(TestComponent); + + schedule.run(&mut world); + let state = world.get_resource::().unwrap(); + assert!(state.single_ran); + assert!(state.populated_ran); } } - fn invalid_system_param_skips_core(executor: ExecutorKind) { - let mut world = World::new(); - let mut schedule = Schedule::default(); - schedule.set_executor_kind(executor); - schedule.add_systems( - ( - // This system depends on a system that is always skipped. - (|mut commands: Commands| { - commands.insert_resource(R2); - }) - .warn_param_missing(), - ) - .chain(), - ); - schedule.run(&mut world); - assert!(world.get_resource::().is_none()); - assert!(world.get_resource::().is_some()); - } - - #[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)] - struct S1; + fn look_for_missing_resource(_res: Res) {} #[test] - fn invalid_condition_param_skips_system() { - for executor in EXECUTORS { - invalid_condition_param_skips_system_core(executor); - } - } - - fn invalid_condition_param_skips_system_core(executor: ExecutorKind) { + #[should_panic] + fn missing_resource_panics_simple() { let mut world = World::new(); let mut schedule = Schedule::default(); - schedule.set_executor_kind(executor); - schedule.configure_sets(S1.run_if((|_: Res| true).warn_param_missing())); - schedule.add_systems(( - // System gets skipped if system set run conditions fail validation. - (|mut commands: Commands| { - commands.insert_resource(R1); - }) - .warn_param_missing() - .in_set(S1), - // System gets skipped if run conditions fail validation. - (|mut commands: Commands| { - commands.insert_resource(R2); - }) - .warn_param_missing() - .run_if((|_: Res| true).warn_param_missing()), - )); + + #[expect(deprecated, reason = "We still need to test this.")] + schedule.set_executor_kind(ExecutorKind::Simple); + schedule.add_systems(look_for_missing_resource); schedule.run(&mut world); - assert!(world.get_resource::().is_none()); - assert!(world.get_resource::().is_none()); + } + + #[test] + #[should_panic] + fn missing_resource_panics_single_threaded() { + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.set_executor_kind(ExecutorKind::SingleThreaded); + schedule.add_systems(look_for_missing_resource); + schedule.run(&mut world); + } + + #[test] + #[should_panic] + fn missing_resource_panics_multi_threaded() { + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.set_executor_kind(ExecutorKind::MultiThreaded); + schedule.add_systems(look_for_missing_resource); + schedule.run(&mut world); + } + + #[test] + fn piped_systems_first_system_skipped() { + // This system should be skipped when run due to no matching entity + fn pipe_out(_single: Single<&TestComponent>) -> u8 { + 42 + } + + fn pipe_in(_input: In, mut counter: ResMut) { + counter.0 += 1; + } + + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + + let counter = world.resource::(); + assert_eq!(counter.0, 0); + } + + #[test] + fn piped_system_second_system_skipped() { + fn pipe_out(mut counter: ResMut) -> u8 { + counter.0 += 1; + 42 + } + + // This system should be skipped when run due to no matching entity + fn pipe_in(_input: In, _single: Single<&TestComponent>) {} + + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + let counter = world.resource::(); + assert_eq!(counter.0, 0); + } + + #[test] + #[should_panic] + fn piped_system_first_system_panics() { + // This system should panic when run because the resource is missing + fn pipe_out(_res: Res) -> u8 { + 42 + } + + fn pipe_in(_input: In) {} + + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + } + + #[test] + #[should_panic] + fn piped_system_second_system_panics() { + fn pipe_out() -> u8 { + 42 + } + + // This system should panic when run because the resource is missing + fn pipe_in(_input: In, _res: Res) {} + + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + } + + // This test runs without panicking because we've + // decided to use early-out behavior for piped systems + #[test] + fn piped_system_skip_and_panic() { + // This system should be skipped when run due to no matching entity + fn pipe_out(_single: Single<&TestComponent>) -> u8 { + 42 + } + + // This system should panic when run because the resource is missing + fn pipe_in(_input: In, _res: Res) {} + + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + } + + #[test] + #[should_panic] + fn piped_system_panic_and_skip() { + // This system should panic when run because the resource is missing + + fn pipe_out(_res: Res) -> u8 { + 42 + } + + // This system should be skipped when run due to no matching entity + fn pipe_in(_input: In, _single: Single<&TestComponent>) {} + + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + } + + #[test] + #[should_panic] + fn piped_system_panic_and_panic() { + // This system should panic when run because the resource is missing + + fn pipe_out(_res: Res) -> u8 { + 42 + } + + // This system should panic when run because the resource is missing + fn pipe_in(_input: In, _res: Res) {} + + let mut world = World::new(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + } + + #[test] + fn piped_system_skip_and_skip() { + // This system should be skipped when run due to no matching entity + + fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut) -> u8 { + counter.0 += 1; + 42 + } + + // This system should be skipped when run due to no matching entity + fn pipe_in(_input: In, _single: Single<&TestComponent>, mut counter: ResMut) { + counter.0 += 1; + } + + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule.add_systems(pipe_out.pipe(pipe_in)); + schedule.run(&mut world); + + let counter = world.resource::(); + assert_eq!(counter.0, 0); } } diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 5589a31e61..e5fd94d2dd 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, vec::Vec}; -use bevy_platform_support::sync::Arc; +use bevy_platform::cell::SyncUnsafeCell; +use bevy_platform::sync::Arc; use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor}; -use bevy_utils::{default, syncunsafecell::SyncUnsafeCell}; use concurrent_queue::ConcurrentQueue; use core::{any::Any, panic::AssertUnwindSafe}; use fixedbitset::FixedBitSet; @@ -13,10 +13,8 @@ use std::sync::{Mutex, MutexGuard}; use tracing::{info_span, Span}; use crate::{ - archetype::ArchetypeComponentId, - error::{BevyError, Result, SystemErrorContext}, + error::{ErrorContext, ErrorHandler, Result}, prelude::Resource, - query::Access, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, system::ScheduleSystem, world::{unsafe_world_cell::UnsafeWorldCell, World}, @@ -62,8 +60,13 @@ impl<'env, 'sys> Environment<'env, 'sys> { /// Per-system data used by the [`MultiThreadedExecutor`]. // Copied here because it can't be read from the system when it's running. struct SystemTaskMetadata { - /// The [`ArchetypeComponentId`] access of the system. - archetype_component_access: Access, + /// The set of systems whose `component_access_set()` conflicts with this one. + conflicting_systems: FixedBitSet, + /// The set of systems whose `component_access_set()` conflicts with this system's conditions. + /// Note that this is separate from `conflicting_systems` to handle the case where + /// a system is skipped by an earlier system set condition or system stepping, + /// and needs access to run its conditions but not for itself. + condition_conflicting_systems: FixedBitSet, /// Indices of the systems that directly depend on the system. dependents: Vec, /// Is `true` if the system does not access `!Send` data. @@ -97,8 +100,8 @@ pub struct MultiThreadedExecutor { pub struct ExecutorState { /// Metadata for scheduling and running system tasks. system_task_metadata: Vec, - /// Union of the accesses of all currently running systems. - active_access: Access, + /// The set of systems whose `component_access_set()` conflicts with this system set's conditions. + set_condition_conflicting_systems: Vec, /// Returns `true` if a system with non-`Send` access is running. local_thread_running: bool, /// Returns `true` if an exclusive system is running. @@ -131,7 +134,7 @@ pub struct ExecutorState { struct Context<'scope, 'env, 'sys> { environment: &'env Environment<'env, 'sys>, scope: &'scope Scope<'scope, 'env, ()>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: ErrorHandler, } impl Default for MultiThreadedExecutor { @@ -164,7 +167,8 @@ impl SystemExecutor for MultiThreadedExecutor { state.system_task_metadata = Vec::with_capacity(sys_count); for index in 0..sys_count { state.system_task_metadata.push(SystemTaskMetadata { - archetype_component_access: default(), + conflicting_systems: FixedBitSet::with_capacity(sys_count), + condition_conflicting_systems: FixedBitSet::with_capacity(sys_count), dependents: schedule.system_dependents[index].clone(), is_send: schedule.systems[index].is_send(), is_exclusive: schedule.systems[index].is_exclusive(), @@ -174,6 +178,60 @@ impl SystemExecutor for MultiThreadedExecutor { } } + { + #[cfg(feature = "trace")] + let _span = info_span!("calculate conflicting systems").entered(); + for index1 in 0..sys_count { + let system1 = &schedule.systems[index1]; + for index2 in 0..index1 { + let system2 = &schedule.systems[index2]; + if !system2 + .component_access_set() + .is_compatible(system1.component_access_set()) + { + state.system_task_metadata[index1] + .conflicting_systems + .insert(index2); + state.system_task_metadata[index2] + .conflicting_systems + .insert(index1); + } + } + + for index2 in 0..sys_count { + let system2 = &schedule.systems[index2]; + if schedule.system_conditions[index1].iter().any(|condition| { + !system2 + .component_access_set() + .is_compatible(condition.component_access_set()) + }) { + state.system_task_metadata[index1] + .condition_conflicting_systems + .insert(index2); + } + } + } + + state.set_condition_conflicting_systems.clear(); + state.set_condition_conflicting_systems.reserve(set_count); + for set_idx in 0..set_count { + let mut conflicting_systems = FixedBitSet::with_capacity(sys_count); + for sys_index in 0..sys_count { + let system = &schedule.systems[sys_index]; + if schedule.set_conditions[set_idx].iter().any(|condition| { + !system + .component_access_set() + .is_compatible(condition.component_access_set()) + }) { + conflicting_systems.insert(sys_index); + } + } + state + .set_condition_conflicting_systems + .push(conflicting_systems); + } + } + state.num_dependencies_remaining = Vec::with_capacity(sys_count); } @@ -182,7 +240,7 @@ impl SystemExecutor for MultiThreadedExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: ErrorHandler, ) { let state = self.state.get_mut().unwrap(); // reset counts @@ -257,7 +315,6 @@ impl SystemExecutor for MultiThreadedExecutor { debug_assert!(state.ready_systems.is_clear()); debug_assert!(state.running_systems.is_clear()); - state.active_access.clear(); state.evaluated_sets.clear(); state.skipped_systems.clear(); state.completed_systems.clear(); @@ -345,9 +402,9 @@ impl ExecutorState { fn new() -> Self { Self { system_task_metadata: Vec::new(), + set_condition_conflicting_systems: Vec::new(), num_running_systems: 0, num_dependencies_remaining: Vec::new(), - active_access: default(), local_thread_running: false, exclusive_running: false, evaluated_sets: FixedBitSet::new(), @@ -368,8 +425,6 @@ impl ExecutorState { self.finish_system_and_handle_dependents(result); } - self.rebuild_active_access(); - // SAFETY: // - `finish_system_and_handle_dependents` has updated the currently running systems. // - `rebuild_active_access` locks access for all currently running systems. @@ -405,12 +460,7 @@ impl ExecutorState { // Therefore, no other reference to this system exists and there is no aliasing. let system = unsafe { &mut *context.environment.systems[system_index].get() }; - if !self.can_run( - system_index, - system, - conditions, - context.environment.world_cell, - ) { + if !self.can_run(system_index, conditions) { // NOTE: exclusive systems with ambiguities are susceptible to // being significantly displaced here (compared to single-threaded order) // if systems after them in topological order can run @@ -421,7 +471,6 @@ impl ExecutorState { self.ready_systems.remove(system_index); // SAFETY: `can_run` returned true, which means that: - // - It must have called `update_archetype_component_access` for each run condition. // - There can be no systems running whose accesses would conflict with any conditions. if unsafe { !self.should_run( @@ -429,6 +478,7 @@ impl ExecutorState { system, conditions, context.environment.world_cell, + context.error_handler, ) } { self.skip_system_and_signal_dependents(system_index); @@ -452,7 +502,8 @@ impl ExecutorState { // SAFETY: // - Caller ensured no other reference to this system exists. - // - `can_run` has been called, which calls `update_archetype_component_access` with this system. + // - `system_task_metadata[system_index].is_exclusive` is `false`, + // so `System::is_exclusive` returned `false` when we called it. // - `can_run` returned true, so no systems with conflicting world access are running. unsafe { self.spawn_system_task(context, system_index); @@ -464,13 +515,7 @@ impl ExecutorState { self.ready_systems_copy = ready_systems; } - fn can_run( - &mut self, - system_index: usize, - system: &mut ScheduleSystem, - conditions: &mut Conditions, - world: UnsafeWorldCell, - ) -> bool { + fn can_run(&mut self, system_index: usize, conditions: &mut Conditions) -> bool { let system_meta = &self.system_task_metadata[system_index]; if system_meta.is_exclusive && self.num_running_systems > 0 { return false; @@ -484,39 +529,24 @@ impl ExecutorState { for set_idx in conditions.sets_with_conditions_of_systems[system_index] .difference(&self.evaluated_sets) { - for condition in &mut conditions.set_conditions[set_idx] { - condition.update_archetype_component_access(world); - if !condition - .archetype_component_access() - .is_compatible(&self.active_access) - { - return false; - } - } - } - - for condition in &mut conditions.system_conditions[system_index] { - condition.update_archetype_component_access(world); - if !condition - .archetype_component_access() - .is_compatible(&self.active_access) - { + if !self.set_condition_conflicting_systems[set_idx].is_disjoint(&self.running_systems) { return false; } } - if !self.skipped_systems.contains(system_index) { - system.update_archetype_component_access(world); - if !system - .archetype_component_access() - .is_compatible(&self.active_access) - { - return false; - } + if !system_meta + .condition_conflicting_systems + .is_disjoint(&self.running_systems) + { + return false; + } - self.system_task_metadata[system_index] - .archetype_component_access - .clone_from(system.archetype_component_access()); + if !self.skipped_systems.contains(system_index) + && !system_meta + .conflicting_systems + .is_disjoint(&self.running_systems) + { + return false; } true @@ -526,14 +556,13 @@ impl ExecutorState { /// * `world` must have permission to read any world data required by /// the system's conditions: this includes conditions for the system /// itself, and conditions for any of the system's sets. - /// * `update_archetype_component` must have been called with `world` - /// for the system as well as system and system set's run conditions. unsafe fn should_run( &mut self, system_index: usize, system: &mut ScheduleSystem, conditions: &mut Conditions, world: UnsafeWorldCell, + error_handler: ErrorHandler, ) -> bool { let mut should_run = !self.skipped_systems.contains(system_index); @@ -546,9 +575,12 @@ impl ExecutorState { // SAFETY: // - The caller ensures that `world` has permission to read any data // required by the conditions. - // - `update_archetype_component_access` has been called for each run condition. let set_conditions_met = unsafe { - evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world) + evaluate_and_fold_conditions( + &mut conditions.set_conditions[set_idx], + world, + error_handler, + ) }; if !set_conditions_met { @@ -564,9 +596,12 @@ impl ExecutorState { // SAFETY: // - The caller ensures that `world` has permission to read any data // required by the conditions. - // - `update_archetype_component_access` has been called for each run condition. let system_conditions_met = unsafe { - evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world) + evaluate_and_fold_conditions( + &mut conditions.system_conditions[system_index], + world, + error_handler, + ) }; if !system_conditions_met { @@ -579,11 +614,25 @@ impl ExecutorState { // SAFETY: // - The caller ensures that `world` has permission to read any data // required by the system. - // - `update_archetype_component_access` has been called for system. - let valid_params = unsafe { system.validate_param_unsafe(world) }; + let valid_params = match unsafe { system.validate_param_unsafe(world) } { + Ok(()) => true, + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: system.name(), + last_run: system.get_last_run(), + }, + ); + } + false + } + }; if !valid_params { self.skipped_systems.insert(system_index); } + should_run &= valid_params; } @@ -592,10 +641,9 @@ impl ExecutorState { /// # Safety /// - Caller must not alias systems that are running. + /// - `is_exclusive` must have returned `false` for the specified system. /// - `world` must have permission to access the world data /// used by the specified system. - /// - `update_archetype_component_access` must have been called with `world` - /// on the system associated with `system_index`. unsafe fn spawn_system_task(&mut self, context: &Context, system_index: usize) { // SAFETY: this system is not running, no other reference exists let system = unsafe { &mut *context.environment.systems[system_index].get() }; @@ -609,7 +657,7 @@ impl ExecutorState { // SAFETY: // - The caller ensures that we have permission to // access the world data used by the system. - // - `update_archetype_component_access` has been called. + // - `is_exclusive` returned false unsafe { if let Err(err) = __rust_begin_short_backtrace::run_unsafe( system, @@ -617,7 +665,7 @@ impl ExecutorState { ) { (context.error_handler)( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, @@ -628,9 +676,6 @@ impl ExecutorState { context.system_completed(system_index, res, system); }; - self.active_access - .extend(&system_meta.archetype_component_access); - if system_meta.is_send { context.scope.spawn(task); } else { @@ -669,7 +714,7 @@ impl ExecutorState { if let Err(err) = __rust_begin_short_backtrace::run(system, world) { (context.error_handler)( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, @@ -721,15 +766,6 @@ impl ExecutorState { } } } - - fn rebuild_active_access(&mut self) { - self.active_access.clear(); - for index in self.running_systems.ones() { - let system_meta = &self.system_task_metadata[index]; - self.active_access - .extend(&system_meta.archetype_component_access); - } - } } fn apply_deferred( @@ -761,11 +797,10 @@ fn apply_deferred( /// # Safety /// - `world` must have permission to read any world data /// required by `conditions`. -/// - `update_archetype_component_access` must have been called -/// with `world` for each condition in `conditions`. unsafe fn evaluate_and_fold_conditions( conditions: &mut [BoxedCondition], world: UnsafeWorldCell, + error_handler: ErrorHandler, ) -> bool { #[expect( clippy::unnecessary_fold, @@ -777,14 +812,24 @@ unsafe fn evaluate_and_fold_conditions( // SAFETY: // - The caller ensures that `world` has permission to read any data // required by the condition. - // - `update_archetype_component_access` has been called for condition. - if !unsafe { condition.validate_param_unsafe(world) } { - return false; + match unsafe { condition.validate_param_unsafe(world) } { + Ok(()) => (), + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: condition.name(), + last_run: condition.get_last_run(), + }, + ); + } + return false; + } } // SAFETY: // - The caller ensures that `world` has permission to read any data // required by the condition. - // - `update_archetype_component_access` has been called for condition. unsafe { __rust_begin_short_backtrace::readonly_run_unsafe(&mut **condition, world) } }) .fold(true, |acc, res| acc && res) diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 9570e98d09..584c5a1073 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -1,3 +1,5 @@ +#![expect(deprecated, reason = "Everything here is deprecated")] + use core::panic::AssertUnwindSafe; use fixedbitset::FixedBitSet; @@ -8,7 +10,7 @@ use tracing::info_span; use std::eprintln; use crate::{ - error::{BevyError, SystemErrorContext}, + error::{ErrorContext, ErrorHandler}, schedule::{ executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, }, @@ -20,6 +22,10 @@ use super::__rust_begin_short_backtrace; /// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls /// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system. #[derive(Default)] +#[deprecated( + since = "0.17.0", + note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." +)] pub struct SimpleExecutor { /// Systems sets whose conditions have been evaluated. evaluated_sets: FixedBitSet, @@ -44,7 +50,7 @@ impl SystemExecutor for SimpleExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: ErrorHandler, ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -67,8 +73,11 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system set's conditions - let set_conditions_met = - evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); + let set_conditions_met = evaluate_and_fold_conditions( + &mut schedule.set_conditions[set_idx], + world, + error_handler, + ); if !set_conditions_met { self.completed_systems @@ -80,14 +89,31 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system's conditions - let system_conditions_met = - evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); + let system_conditions_met = evaluate_and_fold_conditions( + &mut schedule.system_conditions[system_index], + world, + error_handler, + ); should_run &= system_conditions_met; let system = &mut schedule.systems[system_index]; if should_run { - let valid_params = system.validate_param(world); + let valid_params = match system.validate_param(world) { + Ok(()) => true, + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: system.name(), + last_run: system.get_last_run(), + }, + ); + } + false + } + }; should_run &= valid_params; } @@ -109,7 +135,7 @@ impl SystemExecutor for SimpleExecutor { if let Err(err) = __rust_begin_short_backtrace::run(system, world) { error_handler( err, - SystemErrorContext { + ErrorContext::System { name: system.name(), last_run: system.get_last_run(), }, @@ -151,8 +177,15 @@ impl SimpleExecutor { } } } - -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { +#[deprecated( + since = "0.17.0", + note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation." +)] +fn evaluate_and_fold_conditions( + conditions: &mut [BoxedCondition], + world: &mut World, + error_handler: ErrorHandler, +) -> bool { #[expect( clippy::unnecessary_fold, reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." @@ -160,8 +193,20 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W conditions .iter_mut() .map(|condition| { - if !condition.validate_param(world) { - return false; + match condition.validate_param(world) { + Ok(()) => (), + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: condition.name(), + last_run: condition.get_last_run(), + }, + ); + } + return false; + } } __rust_begin_short_backtrace::readonly_run(&mut **condition, world) }) diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index 3f3d579cc2..0076103637 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -8,7 +8,7 @@ use tracing::info_span; use std::eprintln; use crate::{ - error::{BevyError, SystemErrorContext}, + error::{ErrorContext, ErrorHandler}, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -50,7 +50,7 @@ impl SystemExecutor for SingleThreadedExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, - error_handler: fn(BevyError, SystemErrorContext), + error_handler: ErrorHandler, ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -73,8 +73,11 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system set's conditions - let set_conditions_met = - evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); + let set_conditions_met = evaluate_and_fold_conditions( + &mut schedule.set_conditions[set_idx], + world, + error_handler, + ); if !set_conditions_met { self.completed_systems @@ -86,14 +89,32 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system's conditions - let system_conditions_met = - evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); + let system_conditions_met = evaluate_and_fold_conditions( + &mut schedule.system_conditions[system_index], + world, + error_handler, + ); should_run &= system_conditions_met; let system = &mut schedule.systems[system_index]; if should_run { - let valid_params = system.validate_param(world); + let valid_params = match system.validate_param(world) { + Ok(()) => true, + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: system.name(), + last_run: system.get_last_run(), + }, + ); + } + false + } + }; + should_run &= valid_params; } @@ -113,33 +134,16 @@ impl SystemExecutor for SingleThreadedExecutor { } let f = AssertUnwindSafe(|| { - if system.is_exclusive() { - if let Err(err) = __rust_begin_short_backtrace::run(system, world) { - error_handler( - err, - SystemErrorContext { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - } else { - // Use run_unsafe to avoid immediately applying deferred buffers - let world = world.as_unsafe_world_cell(); - system.update_archetype_component_access(world); - // SAFETY: We have exclusive, single-threaded access to the world and - // update_archetype_component_access is being called immediately before this. - unsafe { - if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) { - error_handler( - err, - SystemErrorContext { - name: system.name(), - last_run: system.get_last_run(), - }, - ); - } - }; + if let Err(err) = + __rust_begin_short_backtrace::run_without_applying_deferred(system, world) + { + error_handler( + err, + ErrorContext::System { + name: system.name(), + last_run: system.get_last_run(), + }, + ); } }); @@ -195,7 +199,11 @@ impl SingleThreadedExecutor { } } -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { +fn evaluate_and_fold_conditions( + conditions: &mut [BoxedCondition], + world: &mut World, + error_handler: ErrorHandler, +) -> bool { #[expect( clippy::unnecessary_fold, reason = "Short-circuiting here would prevent conditions from mutating their own state as needed." @@ -203,8 +211,20 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut W conditions .iter_mut() .map(|condition| { - if !condition.validate_param(world) { - return false; + match condition.validate_param(world) { + Ok(()) => (), + Err(e) => { + if !e.skipped { + error_handler( + e.into(), + ErrorContext::System { + name: condition.name(), + last_run: condition.get_last_run(), + }, + ); + } + return false; + } } __rust_begin_short_backtrace::readonly_run(&mut **condition, world) }) diff --git a/crates/bevy_ecs/src/schedule/graph/graph_map.rs b/crates/bevy_ecs/src/schedule/graph/graph_map.rs index b255e55d26..d2fbde9995 100644 --- a/crates/bevy_ecs/src/schedule/graph/graph_map.rs +++ b/crates/bevy_ecs/src/schedule/graph/graph_map.rs @@ -5,7 +5,7 @@ //! [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/ use alloc::vec::Vec; -use bevy_platform_support::{collections::HashSet, hash::FixedHasher}; +use bevy_platform::{collections::HashSet, hash::FixedHasher}; use core::{ fmt, hash::{BuildHasher, Hash}, diff --git a/crates/bevy_ecs/src/schedule/graph/mod.rs b/crates/bevy_ecs/src/schedule/graph/mod.rs index 3fb5c47a0d..8a98604102 100644 --- a/crates/bevy_ecs/src/schedule/graph/mod.rs +++ b/crates/bevy_ecs/src/schedule/graph/mod.rs @@ -5,7 +5,7 @@ use core::{ }; use smallvec::SmallVec; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_utils::TypeIdMap; use fixedbitset::FixedBitSet; diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index d7a66faa63..81912d2f72 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -37,7 +37,7 @@ mod tests { }; #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] - enum TestSet { + enum TestSystems { A, B, C, @@ -142,7 +142,7 @@ mod tests { make_function_system(1).before(named_system), make_function_system(0) .after(named_system) - .in_set(TestSet::A), + .in_set(TestSystems::A), )); schedule.run(&mut world); @@ -153,12 +153,12 @@ mod tests { assert_eq!(world.resource::().0, vec![]); // modify the schedule after it's been initialized and test ordering with sets - schedule.configure_sets(TestSet::A.after(named_system)); + schedule.configure_sets(TestSystems::A.after(named_system)); schedule.add_systems(( make_function_system(3) - .before(TestSet::A) + .before(TestSystems::A) .after(named_system), - make_function_system(4).after(TestSet::A), + make_function_system(4).after(TestSystems::A), )); schedule.run(&mut world); @@ -347,14 +347,14 @@ mod tests { world.init_resource::(); - schedule.configure_sets(TestSet::A.run_if(|| false).run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSet::A)); - schedule.configure_sets(TestSet::B.run_if(|| true).run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSet::B)); - schedule.configure_sets(TestSet::C.run_if(|| false).run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSet::C)); - schedule.configure_sets(TestSet::D.run_if(|| true).run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSet::D)); + schedule.configure_sets(TestSystems::A.run_if(|| false).run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSystems::A)); + schedule.configure_sets(TestSystems::B.run_if(|| true).run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSystems::B)); + schedule.configure_sets(TestSystems::C.run_if(|| false).run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSystems::C)); + schedule.configure_sets(TestSystems::D.run_if(|| true).run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSystems::D)); schedule.run(&mut world); assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); @@ -367,14 +367,14 @@ mod tests { world.init_resource::(); - schedule.configure_sets(TestSet::A.run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSet::A).run_if(|| false)); - schedule.configure_sets(TestSet::B.run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSet::B).run_if(|| false)); - schedule.configure_sets(TestSet::C.run_if(|| false)); - schedule.add_systems(counting_system.in_set(TestSet::C).run_if(|| true)); - schedule.configure_sets(TestSet::D.run_if(|| true)); - schedule.add_systems(counting_system.in_set(TestSet::D).run_if(|| true)); + schedule.configure_sets(TestSystems::A.run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSystems::A).run_if(|| false)); + schedule.configure_sets(TestSystems::B.run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSystems::B).run_if(|| false)); + schedule.configure_sets(TestSystems::C.run_if(|| false)); + schedule.add_systems(counting_system.in_set(TestSystems::C).run_if(|| true)); + schedule.configure_sets(TestSystems::D.run_if(|| true)); + schedule.add_systems(counting_system.in_set(TestSystems::D).run_if(|| true)); schedule.run(&mut world); assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); @@ -440,12 +440,12 @@ mod tests { let mut schedule = Schedule::default(); schedule.configure_sets( - TestSet::A + TestSystems::A .run_if(|res1: Res| res1.is_changed()) .run_if(|res2: Res| res2.is_changed()), ); - schedule.add_systems(counting_system.in_set(TestSet::A)); + schedule.add_systems(counting_system.in_set(TestSystems::A)); // both resource were just added. schedule.run(&mut world); @@ -489,13 +489,14 @@ mod tests { world.init_resource::(); let mut schedule = Schedule::default(); - schedule - .configure_sets(TestSet::A.run_if(|res1: Res| res1.is_changed())); + schedule.configure_sets( + TestSystems::A.run_if(|res1: Res| res1.is_changed()), + ); schedule.add_systems( counting_system .run_if(|res2: Res| res2.is_changed()) - .in_set(TestSet::A), + .in_set(TestSystems::A), ); // both resource were just added. @@ -537,7 +538,7 @@ mod tests { #[should_panic] fn dependency_loop() { let mut schedule = Schedule::default(); - schedule.configure_sets(TestSet::X.after(TestSet::X)); + schedule.configure_sets(TestSystems::X.after(TestSystems::X)); } #[test] @@ -545,8 +546,8 @@ mod tests { let mut world = World::new(); let mut schedule = Schedule::default(); - schedule.configure_sets(TestSet::A.after(TestSet::B)); - schedule.configure_sets(TestSet::B.after(TestSet::A)); + schedule.configure_sets(TestSystems::A.after(TestSystems::B)); + schedule.configure_sets(TestSystems::B.after(TestSystems::A)); let result = schedule.initialize(&mut world); assert!(matches!( @@ -572,7 +573,7 @@ mod tests { #[should_panic] fn hierarchy_loop() { let mut schedule = Schedule::default(); - schedule.configure_sets(TestSet::X.in_set(TestSet::X)); + schedule.configure_sets(TestSystems::X.in_set(TestSystems::X)); } #[test] @@ -580,8 +581,8 @@ mod tests { let mut world = World::new(); let mut schedule = Schedule::default(); - schedule.configure_sets(TestSet::A.in_set(TestSet::B)); - schedule.configure_sets(TestSet::B.in_set(TestSet::A)); + schedule.configure_sets(TestSystems::A.in_set(TestSystems::B)); + schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); let result = schedule.initialize(&mut world); assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle(_)))); @@ -647,13 +648,13 @@ mod tests { }); // Add `A`. - schedule.configure_sets(TestSet::A); + schedule.configure_sets(TestSystems::A); // Add `B` as child of `A`. - schedule.configure_sets(TestSet::B.in_set(TestSet::A)); + schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); // Add `X` as child of both `A` and `B`. - schedule.configure_sets(TestSet::X.in_set(TestSet::A).in_set(TestSet::B)); + schedule.configure_sets(TestSystems::X.in_set(TestSystems::A).in_set(TestSystems::B)); // `X` cannot be the `A`'s child and grandchild at the same time. let result = schedule.initialize(&mut world); @@ -669,8 +670,8 @@ mod tests { let mut schedule = Schedule::default(); // Add `B` and give it both kinds of relationships with `A`. - schedule.configure_sets(TestSet::B.in_set(TestSet::A)); - schedule.configure_sets(TestSet::B.after(TestSet::A)); + schedule.configure_sets(TestSystems::B.in_set(TestSystems::A)); + schedule.configure_sets(TestSystems::B.after(TestSystems::A)); let result = schedule.initialize(&mut world); assert!(matches!( result, @@ -686,13 +687,13 @@ mod tests { fn foo() {} // Add `foo` to both `A` and `C`. - schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C)); + schedule.add_systems(foo.in_set(TestSystems::A).in_set(TestSystems::C)); // Order `A -> B -> C`. schedule.configure_sets(( - TestSet::A, - TestSet::B.after(TestSet::A), - TestSet::C.after(TestSet::B), + TestSystems::A, + TestSystems::B.after(TestSystems::A), + TestSystems::C.after(TestSystems::B), )); let result = schedule.initialize(&mut world); @@ -1097,28 +1098,38 @@ mod tests { let ambiguities: Vec<_> = schedule .graph() .conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) + .map(|item| { + ( + item.0, + item.1, + item.2 + .into_iter() + .map(|name| name.to_string()) + .collect::>(), + ) + }) .collect(); let expected = &[ ( "system_d".to_string(), "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], + vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], ), ( "system_d".to_string(), "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], + vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], ), ( "system_b".to_string(), "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], + vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], ), ( "system_b".to_string(), "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], + vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], ), ]; @@ -1146,6 +1157,16 @@ mod tests { let ambiguities: Vec<_> = schedule .graph() .conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) + .map(|item| { + ( + item.0, + item.1, + item.2 + .into_iter() + .map(|name| name.to_string()) + .collect::>(), + ) + }) .collect(); assert_eq!( @@ -1153,7 +1174,7 @@ mod tests { ( "resmut_system (in set (resmut_system, resmut_system))".to_string(), "resmut_system (in set (resmut_system, resmut_system))".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], + vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], ) ); } @@ -1217,6 +1238,7 @@ mod tests { /// verify the [`SimpleExecutor`] supports stepping #[test] + #[expect(deprecated, reason = "We still need to test this.")] fn simple_executor() { assert_executor_supports_stepping!(ExecutorKind::Simple); } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index e82c4132ec..144ce6516c 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -2,6 +2,7 @@ clippy::module_inception, reason = "This instance of module inception is being discussed; see #17344." )] +use alloc::borrow::Cow; use alloc::{ boxed::Box, collections::{BTreeMap, BTreeSet}, @@ -10,7 +11,7 @@ use alloc::{ vec, vec::Vec, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_utils::{default, TypeIdMap}; use core::{ any::{Any, TypeId}, @@ -26,7 +27,6 @@ use tracing::info_span; use crate::{ component::{ComponentId, Components, Tick}, - error::{BevyError, DefaultSystemErrorHandler, SystemErrorContext}, prelude::Component, resource::Resource, schedule::*, @@ -217,6 +217,7 @@ impl Schedules { fn make_executor(kind: ExecutorKind) -> Box { match kind { + #[expect(deprecated, reason = "We still need to support this.")] ExecutorKind::Simple => Box::new(SimpleExecutor::new()), ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), #[cfg(feature = "std")] @@ -256,8 +257,16 @@ impl Chain { /// A collection of systems, and the metadata and executor needed to run them /// in a certain order under certain conditions. /// +/// # Schedule labels +/// +/// Each schedule has a [`ScheduleLabel`] value. This value is used to uniquely identify the +/// schedule when added to a [`World`]’s [`Schedules`], and may be used to specify which schedule +/// a system should be added to. +/// /// # Example +/// /// Here is an example of a `Schedule` running a "Hello world" system: +/// /// ``` /// # use bevy_ecs::prelude::*; /// fn hello_world() { println!("Hello world!") } @@ -272,6 +281,7 @@ impl Chain { /// ``` /// /// A schedule can also run several systems in an ordered way: +/// /// ``` /// # use bevy_ecs::prelude::*; /// fn system_one() { println!("System 1 works!") } @@ -290,13 +300,38 @@ impl Chain { /// schedule.run(&mut world); /// } /// ``` +/// +/// Schedules are often inserted into a [`World`] and identified by their [`ScheduleLabel`] only: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::schedule::ScheduleLabel; +/// +/// // Declare a new schedule label. +/// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] +/// struct Update; +/// +/// // This system shall be part of the schedule. +/// fn an_update_system() { +/// println!("Hello world!"); +/// } +/// +/// fn main() { +/// let mut world = World::new(); +/// +/// // Add a system to the schedule with that label (creating it automatically). +/// world.get_resource_or_init::().add_systems(Update, an_update_system); +/// +/// // Run the schedule, and therefore run the system. +/// world.run_schedule(Update); +/// } +/// ``` pub struct Schedule { label: InternedScheduleLabel, graph: ScheduleGraph, executable: SystemSchedule, executor: Box, executor_initialized: bool, - error_handler: Option, } #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] @@ -321,14 +356,14 @@ impl Schedule { executable: SystemSchedule::new(), executor: make_executor(ExecutorKind::default()), executor_initialized: false, - error_handler: None, }; // Call `set_build_settings` to add any default build passes this.set_build_settings(Default::default()); this } - /// Get the `InternedScheduleLabel` for this `Schedule`. + /// Returns the [`InternedScheduleLabel`] for this `Schedule`, + /// corresponding to the [`ScheduleLabel`] this schedule was created with. pub fn label(&self) -> InternedScheduleLabel { self.label } @@ -395,9 +430,21 @@ impl Schedule { } /// Changes miscellaneous build settings. + /// + /// If [`settings.auto_insert_apply_deferred`][ScheduleBuildSettings::auto_insert_apply_deferred] + /// is `false`, this clears `*_ignore_deferred` edge settings configured so far. + /// + /// Generally this method should be used before adding systems or set configurations to the schedule, + /// not after. pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { if settings.auto_insert_apply_deferred { - self.add_build_pass(passes::AutoInsertApplyDeferredPass::default()); + if !self + .graph + .passes + .contains_key(&TypeId::of::()) + { + self.add_build_pass(passes::AutoInsertApplyDeferredPass::default()); + } } else { self.remove_build_pass::(); } @@ -405,13 +452,6 @@ impl Schedule { self } - /// Set the error handler to use for systems that return a [`Result`](crate::error::Result). - /// - /// See the [`error` module-level documentation](crate::error) for more information. - pub fn set_error_handler(&mut self, error_handler: fn(BevyError, SystemErrorContext)) { - self.error_handler = Some(error_handler); - } - /// Returns the schedule's current `ScheduleBuildSettings`. pub fn get_build_settings(&self) -> ScheduleBuildSettings { self.graph.settings.clone() @@ -449,7 +489,7 @@ impl Schedule { self.initialize(world) .unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label)); - let error_handler = self.error_handler.expect("schedule initialized"); + let error_handler = world.default_error_handler(); #[cfg(not(feature = "bevy_debug_stepping"))] self.executor @@ -492,10 +532,6 @@ impl Schedule { self.executor_initialized = false; } - if self.error_handler.is_none() { - self.error_handler = Some(world.get_resource_or_init::().0); - } - if !self.executor_initialized { self.executor.init(&self.executable); self.executor_initialized = true; @@ -1915,7 +1951,7 @@ impl ScheduleGraph { &'a self, ambiguities: &'a [(NodeId, NodeId, Vec)], components: &'a Components, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator>)> + 'a { ambiguities .iter() .map(move |(system_a, system_b, conflicts)| { @@ -2072,6 +2108,7 @@ mod tests { use bevy_ecs_macros::ScheduleLabel; use crate::{ + error::{ignore, panic, DefaultErrorHandler, Result}, prelude::{ApplyDeferred, Res, Resource}, schedule::{ tests::ResMut, IntoScheduleConfigs, Schedule, ScheduleBuildSettings, SystemSet, @@ -2088,6 +2125,46 @@ mod tests { #[derive(Resource)] struct Resource2; + #[test] + fn unchanged_auto_insert_apply_deferred_has_no_effect() { + use alloc::{vec, vec::Vec}; + + #[derive(PartialEq, Debug)] + enum Entry { + System(usize), + SyncPoint(usize), + } + + #[derive(Resource, Default)] + struct Log(Vec); + + fn system(mut res: ResMut, mut commands: Commands) { + res.0.push(Entry::System(N)); + commands + .queue(|world: &mut World| world.resource_mut::().0.push(Entry::SyncPoint(N))); + } + + let mut world = World::default(); + world.init_resource::(); + let mut schedule = Schedule::default(); + schedule.add_systems((system::<1>, system::<2>).chain_ignore_deferred()); + schedule.set_build_settings(ScheduleBuildSettings { + auto_insert_apply_deferred: true, + ..Default::default() + }); + schedule.run(&mut world); + let actual = world.remove_resource::().unwrap().0; + + let expected = vec![ + Entry::System(1), + Entry::System(2), + Entry::SyncPoint(1), + Entry::SyncPoint(2), + ]; + + assert_eq!(actual, expected); + } + // regression test for https://github.com/bevyengine/bevy/issues/9114 #[test] fn ambiguous_with_not_breaking_run_conditions() { @@ -2821,4 +2898,32 @@ mod tests { .expect("CheckSystemRan Resource Should Exist"); assert_eq!(value.0, 2); } + + #[test] + fn test_default_error_handler() { + #[derive(Resource, Default)] + struct Ran(bool); + + fn system(mut ran: ResMut) -> Result { + ran.0 = true; + Err("I failed!".into()) + } + + // Test that the default error handler is used + let mut world = World::default(); + world.init_resource::(); + world.insert_resource(DefaultErrorHandler(ignore)); + let mut schedule = Schedule::default(); + schedule.add_systems(system).run(&mut world); + assert!(world.resource::().0); + + // Test that the handler doesn't change within the schedule + schedule.add_systems( + (|world: &mut World| { + world.insert_resource(DefaultErrorHandler(panic)); + }) + .before(system), + ); + schedule.run(&mut world); + } } diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 896c7ed050..4974be5d43 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -19,7 +19,39 @@ use crate::{ }; define_label!( - /// A strongly-typed class of labels used to identify a [`Schedule`](crate::schedule::Schedule). + /// A strongly-typed class of labels used to identify a [`Schedule`]. + /// + /// Each schedule in a [`World`] has a unique schedule label value, and + /// schedules can be automatically created from labels via [`Schedules::add_systems()`]. + /// + /// # Defining new schedule labels + /// + /// By default, you should use Bevy's premade schedule labels which implement this trait. + /// If you are using [`bevy_ecs`] directly or if you need to run a group of systems outside + /// the existing schedules, you may define your own schedule labels by using + /// `#[derive(ScheduleLabel)]`. + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// use bevy_ecs::schedule::ScheduleLabel; + /// + /// // Declare a new schedule label. + /// #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] + /// struct Update; + /// + /// let mut world = World::new(); + /// + /// // Add a system to the schedule with that label (creating it automatically). + /// fn a_system_function() {} + /// world.get_resource_or_init::().add_systems(Update, a_system_function); + /// + /// // Run the schedule, and therefore run the system. + /// world.run_schedule(Update); + /// ``` + /// + /// [`Schedule`]: crate::schedule::Schedule + /// [`Schedules::add_systems()`]: crate::schedule::Schedules::add_systems + /// [`World`]: crate::world::World #[diagnostic::on_unimplemented( note = "consider annotating `{Self}` with `#[derive(ScheduleLabel)]`" )] @@ -115,15 +147,6 @@ impl SystemSet for SystemTypeSet { fn dyn_clone(&self) -> Box { Box::new(*self) } - - fn as_dyn_eq(&self) -> &dyn DynEq { - self - } - - fn dyn_hash(&self, mut state: &mut dyn Hasher) { - TypeId::of::().hash(&mut state); - self.hash(&mut state); - } } /// A [`SystemSet`] implicitly created when using @@ -146,15 +169,6 @@ impl SystemSet for AnonymousSet { fn dyn_clone(&self) -> Box { Box::new(*self) } - - fn as_dyn_eq(&self) -> &dyn DynEq { - self - } - - fn dyn_hash(&self, mut state: &mut dyn Hasher) { - TypeId::of::().hash(&mut state); - self.hash(&mut state); - } } /// Types that can be converted into a [`SystemSet`]. diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index d8a33159a0..222dfdfcaf 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -4,7 +4,7 @@ use crate::{ system::{IntoSystem, ResMut}, }; use alloc::vec::Vec; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_utils::TypeIdMap; use core::any::TypeId; use fixedbitset::FixedBitSet; @@ -125,7 +125,7 @@ impl core::fmt::Debug for Stepping { if self.action != Action::RunAll { let Cursor { schedule, system } = self.cursor; match self.schedule_order.get(schedule) { - Some(label) => write!(f, "cursor: {:?}[{}], ", label, system)?, + Some(label) => write!(f, "cursor: {label:?}[{system}], ")?, None => write!(f, "cursor: None, ")?, }; } diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index f9bb94ea0e..d5014f2240 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -125,7 +125,7 @@ impl) + Send + Sync + 'static> for SpawnWith { fn spawn(self, world: &mut World, entity: Entity) { - world.entity_mut(entity).with_related(self.0); + world.entity_mut(entity).with_related_entities(self.0); } fn size_hint(&self) -> usize { @@ -235,9 +235,7 @@ pub struct SpawnOneRelated { impl BundleEffect for SpawnOneRelated { fn apply(self, entity: &mut EntityWorldMut) { - entity.with_related::(|s| { - s.spawn(self.bundle); - }); + entity.with_related::(self.bundle); } } @@ -358,6 +356,151 @@ impl SpawnRelated for T { #[macro_export] macro_rules! related { ($relationship_target:ty [$($child:expr),*$(,)?]) => { - <$relationship_target>::spawn(($($crate::spawn::Spawn($child)),*)) + <$relationship_target>::spawn($crate::recursive_spawn!($($child),*)) + }; +} + +// A tail-recursive spawn utility. +// +// Since `SpawnableList` is only implemented for tuples +// up to twelve elements long, this macro will nest +// longer sequences recursively. By default, this recursion +// will top out at around 1400 elements, but it would be +// ill-advised to spawn that many entities with this method. +// +// For spawning large batches of entities at a time, +// consider `SpawnIter` or eagerly spawning with `Commands`. +#[macro_export] +#[doc(hidden)] +macro_rules! recursive_spawn { + // direct expansion + ($a:expr) => { + $crate::spawn::Spawn($a) + }; + ($a:expr, $b:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + ) + }; + ($a:expr, $b:expr, $c:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + $crate::spawn::Spawn($h), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + $crate::spawn::Spawn($h), + $crate::spawn::Spawn($i), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + $crate::spawn::Spawn($h), + $crate::spawn::Spawn($i), + $crate::spawn::Spawn($j), + ) + }; + ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + $crate::spawn::Spawn($h), + $crate::spawn::Spawn($i), + $crate::spawn::Spawn($j), + $crate::spawn::Spawn($k), + ) + }; + + // recursive expansion + ( + $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, + $g:expr, $h:expr, $i:expr, $j:expr, $k:expr, $($rest:expr),* + ) => { + ( + $crate::spawn::Spawn($a), + $crate::spawn::Spawn($b), + $crate::spawn::Spawn($c), + $crate::spawn::Spawn($d), + $crate::spawn::Spawn($e), + $crate::spawn::Spawn($f), + $crate::spawn::Spawn($g), + $crate::spawn::Spawn($h), + $crate::spawn::Spawn($i), + $crate::spawn::Spawn($j), + $crate::spawn::Spawn($k), + $crate::recursive_spawn!($($rest),*) + ) }; } diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index 2451fccb14..85852a2bea 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -366,6 +366,13 @@ impl BlobVec { unsafe { core::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell, self.len) } } + /// Returns the drop function for values stored in the vector, + /// or `None` if they don't need to be dropped. + #[inline] + pub fn get_drop(&self) -> Option)> { + self.drop + } + /// Clears the vector, removing (and dropping) all values. /// /// Note that this method has no effect on the allocated capacity of the vector. diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index 078acade7d..fa58610bdf 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -1,5 +1,4 @@ use crate::{ - archetype::ArchetypeComponentId, change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick, TickCells}, storage::{blob_vec::BlobVec, SparseSet}, @@ -25,7 +24,6 @@ pub struct ResourceData { expect(dead_code, reason = "currently only used with the std feature") )] type_name: String, - id: ArchetypeComponentId, #[cfg(feature = "std")] origin_thread_id: Option, changed_by: MaybeLocation>>, @@ -67,6 +65,13 @@ impl ResourceData { #[inline] fn validate_access(&self) { if SEND { + #[cfg_attr( + not(feature = "std"), + expect( + clippy::needless_return, + reason = "needless until no_std is addressed (see below)", + ) + )] return; } @@ -84,6 +89,7 @@ impl ResourceData { // TODO: Handle no_std non-send. // Currently, no_std is single-threaded only, so this is safe to ignore. // To support no_std multithreading, an alternative will be required. + // Remove the #[expect] attribute above when this is addressed. } /// Returns true if the resource is populated. @@ -92,12 +98,6 @@ impl ResourceData { !self.data.is_empty() } - /// Gets the [`ArchetypeComponentId`] for the resource. - #[inline] - pub fn id(&self) -> ArchetypeComponentId { - self.id - } - /// Returns a reference to the resource, if it exists. /// /// # Panics @@ -363,7 +363,6 @@ impl Resources { &mut self, component_id: ComponentId, components: &Components, - f: impl FnOnce() -> ArchetypeComponentId, ) -> &mut ResourceData { self.resources.get_or_insert_with(component_id, || { let component_info = components.get_info(component_id).unwrap(); @@ -387,7 +386,6 @@ impl Resources { added_ticks: UnsafeCell::new(Tick::new(0)), changed_ticks: UnsafeCell::new(Tick::new(0)), type_name: String::from(component_info.name()), - id: f(), #[cfg(feature = "std")] origin_thread_id: None, changed_by: MaybeLocation::caller().map(UnsafeCell::new), diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index bb79382e06..42adcd89dc 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -1,15 +1,13 @@ use crate::{ change_detection::MaybeLocation, component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells}, - entity::Entity, + entity::{Entity, EntityRow}, storage::{Column, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; use bevy_ptr::{OwningPtr, Ptr}; use core::{cell::UnsafeCell, hash::Hash, marker::PhantomData, panic::Location}; -use nonmax::NonMaxUsize; - -type EntityIndex = u32; +use nonmax::{NonMaxU32, NonMaxUsize}; #[derive(Debug)] pub(crate) struct SparseArray { @@ -121,10 +119,10 @@ pub struct ComponentSparseSet { // stored for entities that are alive. The generation is not required, but is stored // in debug builds to validate that access is correct. #[cfg(not(debug_assertions))] - entities: Vec, + entities: Vec, #[cfg(debug_assertions)] entities: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl ComponentSparseSet { @@ -170,20 +168,25 @@ impl ComponentSparseSet { change_tick: Tick, caller: MaybeLocation, ) { - if let Some(&dense_index) = self.sparse.get(entity.index()) { + if let Some(&dense_index) = self.sparse.get(entity.row()) { #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); self.dense.replace(dense_index, value, change_tick, caller); } else { let dense_index = self.dense.len(); self.dense .push(value, ComponentTicks::new(change_tick), caller); - self.sparse - .insert(entity.index(), TableRow::from_usize(dense_index)); + + // SAFETY: This entity row does not exist here yet, so there are no duplicates, + // and the entity index can not be the max, so the length must not be max either. + // To do so would have caused a panic in the entity alloxator. + let table_row = unsafe { TableRow::new(NonMaxU32::new_unchecked(dense_index as u32)) }; + + self.sparse.insert(entity.row(), table_row); #[cfg(debug_assertions)] assert_eq!(self.entities.len(), dense_index); #[cfg(not(debug_assertions))] - self.entities.push(entity.index()); + self.entities.push(entity.row()); #[cfg(debug_assertions)] self.entities.push(entity); } @@ -194,16 +197,16 @@ impl ComponentSparseSet { pub fn contains(&self, entity: Entity) -> bool { #[cfg(debug_assertions)] { - if let Some(&dense_index) = self.sparse.get(entity.index()) { + if let Some(&dense_index) = self.sparse.get(entity.row()) { #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); true } else { false } } #[cfg(not(debug_assertions))] - self.sparse.contains(entity.index()) + self.sparse.contains(entity.row()) } /// Returns a reference to the entity's component value. @@ -211,9 +214,9 @@ impl ComponentSparseSet { /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get(&self, entity: Entity) -> Option> { - self.sparse.get(entity.index()).map(|&dense_index| { + self.sparse.get(entity.row()).map(|&dense_index| { #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { self.dense.get_data_unchecked(dense_index) } }) @@ -231,9 +234,9 @@ impl ComponentSparseSet { TickCells<'_>, MaybeLocation<&UnsafeCell<&'static Location<'static>>>, )> { - let dense_index = *self.sparse.get(entity.index())?; + let dense_index = *self.sparse.get(entity.row())?; #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(( @@ -252,9 +255,9 @@ impl ComponentSparseSet { /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_added_tick(&self, entity: Entity) -> Option<&UnsafeCell> { - let dense_index = *self.sparse.get(entity.index())?; + let dense_index = *self.sparse.get(entity.row())?; #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(self.dense.get_added_tick_unchecked(dense_index)) } } @@ -264,9 +267,9 @@ impl ComponentSparseSet { /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_changed_tick(&self, entity: Entity) -> Option<&UnsafeCell> { - let dense_index = *self.sparse.get(entity.index())?; + let dense_index = *self.sparse.get(entity.row())?; #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(self.dense.get_changed_tick_unchecked(dense_index)) } } @@ -276,9 +279,9 @@ impl ComponentSparseSet { /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] pub fn get_ticks(&self, entity: Entity) -> Option { - let dense_index = *self.sparse.get(entity.index())?; + let dense_index = *self.sparse.get(entity.row())?; #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) } } @@ -292,31 +295,38 @@ impl ComponentSparseSet { entity: Entity, ) -> MaybeLocation>>> { MaybeLocation::new_with_flattened(|| { - let dense_index = *self.sparse.get(entity.index())?; + let dense_index = *self.sparse.get(entity.row())?; #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(self.dense.get_changed_by_unchecked(dense_index)) } }) } + /// Returns the drop function for the component type stored in the sparse set, + /// or `None` if it doesn't need to be dropped. + #[inline] + pub fn get_drop(&self) -> Option)> { + self.dense.get_drop() + } + /// Removes the `entity` from this sparse set and returns a pointer to the associated value (if /// it exists). #[must_use = "The returned pointer must be used to drop the removed component."] pub(crate) fn remove_and_forget(&mut self, entity: Entity) -> Option> { - self.sparse.remove(entity.index()).map(|dense_index| { + self.sparse.remove(entity.row()).map(|dense_index| { #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); - self.entities.swap_remove(dense_index.as_usize()); - let is_last = dense_index.as_usize() == self.dense.len() - 1; + assert_eq!(entity, self.entities[dense_index.index()]); + self.entities.swap_remove(dense_index.index()); + let is_last = dense_index.index() == self.dense.len() - 1; // SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid let (value, _, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) }; if !is_last { - let swapped_entity = self.entities[dense_index.as_usize()]; + let swapped_entity = self.entities[dense_index.index()]; #[cfg(not(debug_assertions))] let index = swapped_entity; #[cfg(debug_assertions)] - let index = swapped_entity.index(); + let index = swapped_entity.row(); *self.sparse.get_mut(index).unwrap() = dense_index; } value @@ -327,21 +337,21 @@ impl ComponentSparseSet { /// /// Returns `true` if `entity` had a component value in the sparse set. pub(crate) fn remove(&mut self, entity: Entity) -> bool { - if let Some(dense_index) = self.sparse.remove(entity.index()) { + if let Some(dense_index) = self.sparse.remove(entity.row()) { #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); - self.entities.swap_remove(dense_index.as_usize()); - let is_last = dense_index.as_usize() == self.dense.len() - 1; + assert_eq!(entity, self.entities[dense_index.index()]); + self.entities.swap_remove(dense_index.index()); + let is_last = dense_index.index() == self.dense.len() - 1; // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { self.dense.swap_remove_unchecked(dense_index); } if !is_last { - let swapped_entity = self.entities[dense_index.as_usize()]; + let swapped_entity = self.entities[dense_index.index()]; #[cfg(not(debug_assertions))] let index = swapped_entity; #[cfg(debug_assertions)] - let index = swapped_entity.index(); + let index = swapped_entity.row(); *self.sparse.get_mut(index).unwrap() = dense_index; } true @@ -652,10 +662,11 @@ mod tests { use super::SparseSets; use crate::{ component::{Component, ComponentDescriptor, ComponentId, ComponentInfo}, - entity::Entity, + entity::{Entity, EntityRow}, storage::SparseSet, }; use alloc::{vec, vec::Vec}; + use nonmax::NonMaxU32; #[derive(Debug, Eq, PartialEq)] struct Foo(usize); @@ -663,11 +674,11 @@ mod tests { #[test] fn sparse_set() { let mut set = SparseSet::::default(); - let e0 = Entity::from_raw(0); - let e1 = Entity::from_raw(1); - let e2 = Entity::from_raw(2); - let e3 = Entity::from_raw(3); - let e4 = Entity::from_raw(4); + let e0 = Entity::from_raw(EntityRow::new(NonMaxU32::new(0).unwrap())); + let e1 = Entity::from_raw(EntityRow::new(NonMaxU32::new(1).unwrap())); + let e2 = Entity::from_raw(EntityRow::new(NonMaxU32::new(2).unwrap())); + let e3 = Entity::from_raw(EntityRow::new(NonMaxU32::new(3).unwrap())); + let e4 = Entity::from_raw(EntityRow::new(NonMaxU32::new(4).unwrap())); set.insert(e1, Foo(1)); set.insert(e2, Foo(2)); diff --git a/crates/bevy_ecs/src/storage/table/column.rs b/crates/bevy_ecs/src/storage/table/column.rs index d4690d264c..78fafe0a26 100644 --- a/crates/bevy_ecs/src/storage/table/column.rs +++ b/crates/bevy_ecs/src/storage/table/column.rs @@ -49,13 +49,13 @@ impl ThinColumn { row: TableRow, ) { self.data - .swap_remove_and_drop_unchecked_nonoverlapping(row.as_usize(), last_element_index); + .swap_remove_and_drop_unchecked_nonoverlapping(row.index(), last_element_index); self.added_ticks - .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + .swap_remove_unchecked_nonoverlapping(row.index(), last_element_index); self.changed_ticks - .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + .swap_remove_unchecked_nonoverlapping(row.index(), last_element_index); self.changed_by.as_mut().map(|changed_by| { - changed_by.swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + changed_by.swap_remove_unchecked_nonoverlapping(row.index(), last_element_index); }); } @@ -71,13 +71,13 @@ impl ThinColumn { row: TableRow, ) { self.data - .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + .swap_remove_and_drop_unchecked(row.index(), last_element_index); self.added_ticks - .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + .swap_remove_and_drop_unchecked(row.index(), last_element_index); self.changed_ticks - .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + .swap_remove_and_drop_unchecked(row.index(), last_element_index); self.changed_by.as_mut().map(|changed_by| { - changed_by.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + changed_by.swap_remove_and_drop_unchecked(row.index(), last_element_index); }); } @@ -94,14 +94,14 @@ impl ThinColumn { ) { let _ = self .data - .swap_remove_unchecked(row.as_usize(), last_element_index); + .swap_remove_unchecked(row.index(), last_element_index); self.added_ticks - .swap_remove_unchecked(row.as_usize(), last_element_index); + .swap_remove_unchecked(row.index(), last_element_index); self.changed_ticks - .swap_remove_unchecked(row.as_usize(), last_element_index); + .swap_remove_unchecked(row.index(), last_element_index); self.changed_by .as_mut() - .map(|changed_by| changed_by.swap_remove_unchecked(row.as_usize(), last_element_index)); + .map(|changed_by| changed_by.swap_remove_unchecked(row.index(), last_element_index)); } /// Call [`realloc`](std::alloc::realloc) to expand / shrink the memory allocation for this [`ThinColumn`] @@ -148,15 +148,12 @@ impl ThinColumn { tick: Tick, caller: MaybeLocation, ) { - self.data.initialize_unchecked(row.as_usize(), data); - *self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick; - *self - .changed_ticks - .get_unchecked_mut(row.as_usize()) - .get_mut() = tick; + self.data.initialize_unchecked(row.index(), data); + *self.added_ticks.get_unchecked_mut(row.index()).get_mut() = tick; + *self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = tick; self.changed_by .as_mut() - .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .map(|changed_by| changed_by.get_unchecked_mut(row.index()).get_mut()) .assign(caller); } @@ -173,14 +170,11 @@ impl ThinColumn { change_tick: Tick, caller: MaybeLocation, ) { - self.data.replace_unchecked(row.as_usize(), data); - *self - .changed_ticks - .get_unchecked_mut(row.as_usize()) - .get_mut() = change_tick; + self.data.replace_unchecked(row.index(), data); + *self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = change_tick; self.changed_by .as_mut() - .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .map(|changed_by| changed_by.get_unchecked_mut(row.index()).get_mut()) .assign(caller); } @@ -206,25 +200,25 @@ impl ThinColumn { // Init the data let src_val = other .data - .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); - self.data.initialize_unchecked(dst_row.as_usize(), src_val); + .swap_remove_unchecked(src_row.index(), other_last_element_index); + self.data.initialize_unchecked(dst_row.index(), src_val); // Init added_ticks let added_tick = other .added_ticks - .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); + .swap_remove_unchecked(src_row.index(), other_last_element_index); self.added_ticks - .initialize_unchecked(dst_row.as_usize(), added_tick); + .initialize_unchecked(dst_row.index(), added_tick); // Init changed_ticks let changed_tick = other .changed_ticks - .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); + .swap_remove_unchecked(src_row.index(), other_last_element_index); self.changed_ticks - .initialize_unchecked(dst_row.as_usize(), changed_tick); + .initialize_unchecked(dst_row.index(), changed_tick); self.changed_by.as_mut().zip(other.changed_by.as_mut()).map( |(self_changed_by, other_changed_by)| { let changed_by = other_changed_by - .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); - self_changed_by.initialize_unchecked(dst_row.as_usize(), changed_by); + .swap_remove_unchecked(src_row.index(), other_last_element_index); + self_changed_by.initialize_unchecked(dst_row.index(), changed_by); }, ); } @@ -384,15 +378,12 @@ impl Column { change_tick: Tick, caller: MaybeLocation, ) { - debug_assert!(row.as_usize() < self.len()); - self.data.replace_unchecked(row.as_usize(), data); - *self - .changed_ticks - .get_unchecked_mut(row.as_usize()) - .get_mut() = change_tick; + debug_assert!(row.index() < self.len()); + self.data.replace_unchecked(row.index(), data); + *self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = change_tick; self.changed_by .as_mut() - .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .map(|changed_by| changed_by.get_unchecked_mut(row.index()).get_mut()) .assign(caller); } @@ -419,12 +410,12 @@ impl Column { /// `row` must be within the range `[0, self.len())`. #[inline] pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { - self.data.swap_remove_and_drop_unchecked(row.as_usize()); - self.added_ticks.swap_remove(row.as_usize()); - self.changed_ticks.swap_remove(row.as_usize()); + self.data.swap_remove_and_drop_unchecked(row.index()); + self.added_ticks.swap_remove(row.index()); + self.changed_ticks.swap_remove(row.index()); self.changed_by .as_mut() - .map(|changed_by| changed_by.swap_remove(row.as_usize())); + .map(|changed_by| changed_by.swap_remove(row.index())); } /// Removes an element from the [`Column`] and returns it and its change detection ticks. @@ -444,13 +435,13 @@ impl Column { &mut self, row: TableRow, ) -> (OwningPtr<'_>, ComponentTicks, MaybeLocation) { - let data = self.data.swap_remove_and_forget_unchecked(row.as_usize()); - let added = self.added_ticks.swap_remove(row.as_usize()).into_inner(); - let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner(); + let data = self.data.swap_remove_and_forget_unchecked(row.index()); + let added = self.added_ticks.swap_remove(row.index()).into_inner(); + let changed = self.changed_ticks.swap_remove(row.index()).into_inner(); let caller = self .changed_by .as_mut() - .map(|changed_by| changed_by.swap_remove(row.as_usize()).into_inner()); + .map(|changed_by| changed_by.swap_remove(row.index()).into_inner()); (data, ComponentTicks { added, changed }, caller) } @@ -520,15 +511,15 @@ impl Column { /// Returns `None` if `row` is out of bounds. #[inline] pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> { - (row.as_usize() < self.data.len()) + (row.index() < self.data.len()) // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through a read-only reference to the column. .then(|| unsafe { ( - self.data.get_unchecked(row.as_usize()), + self.data.get_unchecked(row.index()), TickCells { - added: self.added_ticks.get_unchecked(row.as_usize()), - changed: self.changed_ticks.get_unchecked(row.as_usize()), + added: self.added_ticks.get_unchecked(row.index()), + changed: self.changed_ticks.get_unchecked(row.index()), }, ) }) @@ -539,10 +530,10 @@ impl Column { /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_data(&self, row: TableRow) -> Option> { - (row.as_usize() < self.data.len()).then(|| { + (row.index() < self.data.len()).then(|| { // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through a read-only reference to the column. - unsafe { self.data.get_unchecked(row.as_usize()) } + unsafe { self.data.get_unchecked(row.index()) } }) } @@ -554,8 +545,8 @@ impl Column { /// - no other mutable reference to the data of the same row can exist at the same time #[inline] pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { - debug_assert!(row.as_usize() < self.data.len()); - self.data.get_unchecked(row.as_usize()) + debug_assert!(row.index() < self.data.len()); + self.data.get_unchecked(row.index()) } /// Fetches a mutable reference to the data at `row`. @@ -563,10 +554,10 @@ impl Column { /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_data_mut(&mut self, row: TableRow) -> Option> { - (row.as_usize() < self.data.len()).then(|| { + (row.index() < self.data.len()).then(|| { // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through an exclusive reference to the column. - unsafe { self.data.get_unchecked_mut(row.as_usize()) } + unsafe { self.data.get_unchecked_mut(row.index()) } }) } @@ -579,7 +570,7 @@ impl Column { /// adhere to the safety invariants of [`UnsafeCell`]. #[inline] pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell> { - self.added_ticks.get(row.as_usize()) + self.added_ticks.get(row.index()) } /// Fetches the "changed" change detection tick for the value at `row`. @@ -591,7 +582,7 @@ impl Column { /// adhere to the safety invariants of [`UnsafeCell`]. #[inline] pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell> { - self.changed_ticks.get(row.as_usize()) + self.changed_ticks.get(row.index()) } /// Fetches the change detection ticks for the value at `row`. @@ -599,7 +590,7 @@ impl Column { /// Returns `None` if `row` is out of bounds. #[inline] pub fn get_ticks(&self, row: TableRow) -> Option { - if row.as_usize() < self.data.len() { + if row.index() < self.data.len() { // SAFETY: The size of the column has already been checked. Some(unsafe { self.get_ticks_unchecked(row) }) } else { @@ -614,8 +605,8 @@ impl Column { /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { - debug_assert!(row.as_usize() < self.added_ticks.len()); - self.added_ticks.get_unchecked(row.as_usize()) + debug_assert!(row.index() < self.added_ticks.len()); + self.added_ticks.get_unchecked(row.index()) } /// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`] @@ -625,8 +616,8 @@ impl Column { /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { - debug_assert!(row.as_usize() < self.changed_ticks.len()); - self.changed_ticks.get_unchecked(row.as_usize()) + debug_assert!(row.index() < self.changed_ticks.len()); + self.changed_ticks.get_unchecked(row.index()) } /// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`] @@ -636,11 +627,11 @@ impl Column { /// `row` must be within the range `[0, self.len())`. #[inline] pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { - debug_assert!(row.as_usize() < self.added_ticks.len()); - debug_assert!(row.as_usize() < self.changed_ticks.len()); + debug_assert!(row.index() < self.added_ticks.len()); + debug_assert!(row.index() < self.changed_ticks.len()); ComponentTicks { - added: self.added_ticks.get_unchecked(row.as_usize()).read(), - changed: self.changed_ticks.get_unchecked(row.as_usize()).read(), + added: self.added_ticks.get_unchecked(row.index()).read(), + changed: self.changed_ticks.get_unchecked(row.index()).read(), } } @@ -678,7 +669,7 @@ impl Column { ) -> MaybeLocation>>> { self.changed_by .as_ref() - .map(|changed_by| changed_by.get(row.as_usize())) + .map(|changed_by| changed_by.get(row.index())) } /// Fetches the calling location that last changed the value at `row`. @@ -693,8 +684,15 @@ impl Column { row: TableRow, ) -> MaybeLocation<&UnsafeCell<&'static Location<'static>>> { self.changed_by.as_ref().map(|changed_by| { - debug_assert!(row.as_usize() < changed_by.len()); - changed_by.get_unchecked(row.as_usize()) + debug_assert!(row.index() < changed_by.len()); + changed_by.get_unchecked(row.index()) }) } + + /// Returns the drop function for elements of the column, + /// or `None` if they don't need to be dropped. + #[inline] + pub fn get_drop(&self) -> Option)> { + self.data.get_drop() + } } diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index d211bc10da..cf579de8c7 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -6,7 +6,7 @@ use crate::{ storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, }; use alloc::{boxed::Box, vec, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; pub use column::*; use core::{ @@ -16,6 +16,7 @@ use core::{ ops::{Index, IndexMut}, panic::Location, }; +use nonmax::NonMaxU32; mod column; /// An opaque unique ID for a [`Table`] within a [`World`]. @@ -100,39 +101,30 @@ impl TableId { /// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row /// [`Archetype::table_id`]: crate::archetype::Archetype::table_id #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct TableRow(u32); +#[repr(transparent)] +pub struct TableRow(NonMaxU32); impl TableRow { - pub(crate) const INVALID: TableRow = TableRow(u32::MAX); + // TODO: Deprecate in favor of options, since `INVALID` is, technically, valid. + pub(crate) const INVALID: TableRow = TableRow(NonMaxU32::MAX); - /// Creates a `TableRow`. + /// Creates a [`TableRow`]. #[inline] - pub const fn from_u32(index: u32) -> Self { + pub const fn new(index: NonMaxU32) -> Self { Self(index) } - /// Creates a `TableRow` from a [`usize`] index. - /// - /// # Panics - /// - /// Will panic in debug mode if the provided value does not fit within a [`u32`]. - #[inline] - pub const fn from_usize(index: usize) -> Self { - debug_assert!(index as u32 as usize == index); - Self(index as u32) - } - /// Gets the index of the row as a [`usize`]. #[inline] - pub const fn as_usize(self) -> usize { + pub const fn index(self) -> usize { // usize is at least u32 in Bevy - self.0 as usize + self.0.get() as usize } /// Gets the index of the row as a [`usize`]. #[inline] - pub const fn as_u32(self) -> u32 { - self.0 + pub const fn index_u32(self) -> u32 { + self.0.get() } } @@ -225,9 +217,9 @@ impl Table { /// # Safety /// `row` must be in-bounds (`row.as_usize()` < `self.len()`) pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option { - debug_assert!(row.as_usize() < self.entity_count()); + debug_assert!(row.index_u32() < self.entity_count()); let last_element_index = self.entity_count() - 1; - if row.as_usize() != last_element_index { + if row.index_u32() != last_element_index { // Instead of checking this condition on every `swap_remove` call, we // check it here and use `swap_remove_nonoverlapping`. for col in self.columns.values_mut() { @@ -237,22 +229,26 @@ impl Table { // - `row` != `last_element_index` // - the `len` is kept within `self.entities`, it will update accordingly. unsafe { - col.swap_remove_and_drop_unchecked_nonoverlapping(last_element_index, row); + col.swap_remove_and_drop_unchecked_nonoverlapping( + last_element_index as usize, + row, + ); }; } } else { // If `row.as_usize()` == `last_element_index` than there's no point in removing the component // at `row`, but we still need to drop it. for col in self.columns.values_mut() { - col.drop_last_component(last_element_index); + col.drop_last_component(last_element_index as usize); } } - let is_last = row.as_usize() == last_element_index; - self.entities.swap_remove(row.as_usize()); + let is_last = row.index_u32() == last_element_index; + self.entities.swap_remove(row.index()); if is_last { None } else { - Some(self.entities[row.as_usize()]) + // SAFETY: This was sawp removed and was not last, so it must be in bounds. + unsafe { Some(*self.entities.get_unchecked(row.index())) } } } @@ -269,16 +265,21 @@ impl Table { row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); + debug_assert!(row.index_u32() < self.entity_count()); let last_element_index = self.entity_count() - 1; - let is_last = row.as_usize() == last_element_index; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + let is_last = row.index_u32() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { if let Some(new_column) = new_table.get_column_mut(*component_id) { - new_column.initialize_from_unchecked(column, last_element_index, row, new_row); + new_column.initialize_from_unchecked( + column, + last_element_index as usize, + row, + new_row, + ); } else { // It's the caller's responsibility to drop these cases. - column.swap_remove_and_forget_unchecked(last_element_index, row); + column.swap_remove_and_forget_unchecked(last_element_index as usize, row); } } TableMoveResult { @@ -286,7 +287,8 @@ impl Table { swapped_entity: if is_last { None } else { - Some(self.entities[row.as_usize()]) + // SAFETY: This was sawp removed and was not last, so it must be in bounds. + unsafe { Some(*self.entities.get_unchecked(row.index())) } }, } } @@ -302,15 +304,20 @@ impl Table { row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); + debug_assert!(row.index_u32() < self.entity_count()); let last_element_index = self.entity_count() - 1; - let is_last = row.as_usize() == last_element_index; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + let is_last = row.index_u32() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { if let Some(new_column) = new_table.get_column_mut(*component_id) { - new_column.initialize_from_unchecked(column, last_element_index, row, new_row); + new_column.initialize_from_unchecked( + column, + last_element_index as usize, + row, + new_row, + ); } else { - column.swap_remove_and_drop_unchecked(last_element_index, row); + column.swap_remove_and_drop_unchecked(last_element_index as usize, row); } } TableMoveResult { @@ -318,7 +325,8 @@ impl Table { swapped_entity: if is_last { None } else { - Some(self.entities[row.as_usize()]) + // SAFETY: This was sawp removed and was not last, so it must be in bounds. + unsafe { Some(*self.entities.get_unchecked(row.index())) } }, } } @@ -335,22 +343,23 @@ impl Table { row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); + debug_assert!(row.index_u32() < self.entity_count()); let last_element_index = self.entity_count() - 1; - let is_last = row.as_usize() == last_element_index; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + let is_last = row.index_u32() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { new_table .get_column_mut(*component_id) .debug_checked_unwrap() - .initialize_from_unchecked(column, last_element_index, row, new_row); + .initialize_from_unchecked(column, last_element_index as usize, row, new_row); } TableMoveResult { new_row, swapped_entity: if is_last { None } else { - Some(self.entities[row.as_usize()]) + // SAFETY: This was sawp removed and was not last, so it must be in bounds. + unsafe { Some(*self.entities.get_unchecked(row.index())) } }, } } @@ -365,7 +374,7 @@ impl Table { component_id: ComponentId, ) -> Option<&[UnsafeCell]> { self.get_column(component_id) - .map(|col| col.get_data_slice(self.entity_count())) + .map(|col| col.get_data_slice(self.entity_count() as usize)) } /// Get the added ticks of the column matching `component_id` as a slice. @@ -375,7 +384,7 @@ impl Table { ) -> Option<&[UnsafeCell]> { self.get_column(component_id) // SAFETY: `self.len()` is guaranteed to be the len of the ticks array - .map(|col| unsafe { col.get_added_ticks_slice(self.entity_count()) }) + .map(|col| unsafe { col.get_added_ticks_slice(self.entity_count() as usize) }) } /// Get the changed ticks of the column matching `component_id` as a slice. @@ -385,7 +394,7 @@ impl Table { ) -> Option<&[UnsafeCell]> { self.get_column(component_id) // SAFETY: `self.len()` is guaranteed to be the len of the ticks array - .map(|col| unsafe { col.get_changed_ticks_slice(self.entity_count()) }) + .map(|col| unsafe { col.get_changed_ticks_slice(self.entity_count() as usize) }) } /// Fetches the calling locations that last changed the each component @@ -396,7 +405,7 @@ impl Table { MaybeLocation::new_with_flattened(|| { self.get_column(component_id) // SAFETY: `self.len()` is guaranteed to be the len of the locations array - .map(|col| unsafe { col.get_changed_by_slice(self.entity_count()) }) + .map(|col| unsafe { col.get_changed_by_slice(self.entity_count() as usize) }) }) } @@ -406,12 +415,12 @@ impl Table { component_id: ComponentId, row: TableRow, ) -> Option<&UnsafeCell> { - (row.as_usize() < self.entity_count()).then_some( + (row.index_u32() < self.entity_count()).then_some( // SAFETY: `row.as_usize()` < `len` unsafe { self.get_column(component_id)? .changed_ticks - .get_unchecked(row.as_usize()) + .get_unchecked(row.index()) }, ) } @@ -422,12 +431,12 @@ impl Table { component_id: ComponentId, row: TableRow, ) -> Option<&UnsafeCell> { - (row.as_usize() < self.entity_count()).then_some( + (row.index_u32() < self.entity_count()).then_some( // SAFETY: `row.as_usize()` < `len` unsafe { self.get_column(component_id)? .added_ticks - .get_unchecked(row.as_usize()) + .get_unchecked(row.index()) }, ) } @@ -439,13 +448,13 @@ impl Table { row: TableRow, ) -> MaybeLocation>>> { MaybeLocation::new_with_flattened(|| { - (row.as_usize() < self.entity_count()).then_some( + (row.index_u32() < self.entity_count()).then_some( // SAFETY: `row.as_usize()` < `len` unsafe { self.get_column(component_id)? .changed_by .as_ref() - .map(|changed_by| changed_by.get_unchecked(row.as_usize())) + .map(|changed_by| changed_by.get_unchecked(row.index())) }, ) }) @@ -461,8 +470,8 @@ impl Table { row: TableRow, ) -> Option { self.get_column(component_id).map(|col| ComponentTicks { - added: col.added_ticks.get_unchecked(row.as_usize()).read(), - changed: col.changed_ticks.get_unchecked(row.as_usize()).read(), + added: col.added_ticks.get_unchecked(row.index()).read(), + changed: col.changed_ticks.get_unchecked(row.index()).read(), }) } @@ -499,7 +508,7 @@ impl Table { /// Reserves `additional` elements worth of capacity within the table. pub(crate) fn reserve(&mut self, additional: usize) { - if self.capacity() - self.entity_count() < additional { + if (self.capacity() - self.entity_count() as usize) < additional { let column_cap = self.capacity(); self.entities.reserve(additional); @@ -563,10 +572,15 @@ impl Table { /// Allocates space for a new entity /// /// # Safety - /// the allocated row must be written to immediately with valid values in each column + /// + /// The allocated row must be written to immediately with valid values in each column pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow { self.reserve(1); let len = self.entity_count(); + // SAFETY: No entity row may be in more than one table row at once, so there are no duplicates, + // and there can not be an entity row of u32::MAX. Therefore, this can not be max either. + let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(len)) }; + let len = len as usize; self.entities.push(entity); for col in self.columns.values_mut() { col.added_ticks @@ -580,13 +594,16 @@ impl Table { changed_by.initialize_unchecked(len, UnsafeCell::new(caller)); }); } - TableRow::from_usize(len) + + row } /// Gets the number of entities currently being stored in the table. #[inline] - pub fn entity_count(&self) -> usize { - self.entities.len() + pub fn entity_count(&self) -> u32 { + // No entity may have more than one table row, so there are no duplicates, + // and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity. + self.entities.len() as u32 } /// Get the drop function for some component that is stored in this table. @@ -618,7 +635,7 @@ impl Table { /// Call [`Tick::check_tick`] on all of the ticks in the [`Table`] pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { - let len = self.entity_count(); + let len = self.entity_count() as usize; for col in self.columns.values_mut() { // SAFETY: `len` is the actual length of the column unsafe { col.check_change_ticks(len, change_tick) }; @@ -632,7 +649,7 @@ impl Table { /// Clears all of the stored components in the [`Table`]. pub(crate) fn clear(&mut self) { - let len = self.entity_count(); + let len = self.entity_count() as usize; // We must clear the entities first, because in the drop function causes a panic, it will result in a double free of the columns. self.entities.clear(); for column in self.columns.values_mut() { @@ -660,7 +677,7 @@ impl Table { self.get_column_mut(component_id) .debug_checked_unwrap() .data - .get_unchecked_mut(row.as_usize()) + .get_unchecked_mut(row.index()) .promote() } @@ -674,7 +691,7 @@ impl Table { row: TableRow, ) -> Option> { self.get_column(component_id) - .map(|col| col.data.get_unchecked(row.as_usize())) + .map(|col| col.data.get_unchecked(row.index())) } } @@ -806,7 +823,7 @@ impl IndexMut for Tables { impl Drop for Table { fn drop(&mut self) { - let len = self.entity_count(); + let len = self.entity_count() as usize; let cap = self.capacity(); self.entities.clear(); for col in self.columns.values_mut() { @@ -823,11 +840,12 @@ mod tests { use crate::{ change_detection::MaybeLocation, component::{Component, ComponentIds, Components, ComponentsRegistrator, Tick}, - entity::Entity, + entity::{Entity, EntityRow}, ptr::OwningPtr, storage::{TableBuilder, TableId, TableRow, Tables}, }; use alloc::vec::Vec; + use nonmax::NonMaxU32; #[derive(Component)] struct W(T); @@ -856,7 +874,9 @@ mod tests { let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) .build(); - let entities = (0..200).map(Entity::from_raw).collect::>(); + let entities = (0..200) + .map(|index| Entity::from_raw(EntityRow::new(NonMaxU32::new(index).unwrap()))) + .collect::>(); for entity in &entities { // SAFETY: we allocate and immediately set data afterwards unsafe { diff --git a/crates/bevy_ecs/src/system/adapter_system.rs b/crates/bevy_ecs/src/system/adapter_system.rs index 27e812928c..50dbfad7ea 100644 --- a/crates/bevy_ecs/src/system/adapter_system.rs +++ b/crates/bevy_ecs/src/system/adapter_system.rs @@ -1,6 +1,6 @@ use alloc::{borrow::Cow, vec::Vec}; -use super::{IntoSystem, ReadOnlySystem, System}; +use super::{IntoSystem, ReadOnlySystem, System, SystemParamValidationError}; use crate::{ schedule::InternedSystemSet, system::{input::SystemInput, SystemIn}, @@ -131,11 +131,10 @@ where self.system.component_access() } - #[inline] - fn archetype_component_access( + fn component_access_set( &self, - ) -> &crate::query::Access { - self.system.archetype_component_access() + ) -> &crate::query::FilteredAccessSet { + self.system.component_access_set() } fn is_send(&self) -> bool { @@ -162,12 +161,6 @@ where }) } - #[inline] - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut crate::prelude::World) -> Self::Out { - self.func - .adapt(input, |input| self.system.run(input, world)) - } - #[inline] fn apply_deferred(&mut self, world: &mut crate::prelude::World) { self.system.apply_deferred(world); @@ -179,7 +172,10 @@ where } #[inline] - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { // SAFETY: Delegate to other `System` implementations. unsafe { self.system.validate_param_unsafe(world) } } @@ -188,11 +184,6 @@ where self.system.initialize(world); } - #[inline] - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - self.system.update_archetype_component_access(world); - } - fn check_change_tick(&mut self, change_tick: crate::component::Tick) { self.system.check_change_tick(change_tick); } diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 6261b9e355..f9c96c6284 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -1,5 +1,5 @@ use alloc::{boxed::Box, vec::Vec}; -use bevy_utils::synccell::SyncCell; +use bevy_platform::cell::SyncCell; use variadics_please::all_tuples; use crate::{ @@ -8,6 +8,7 @@ use crate::{ resource::Resource, system::{ DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, + SystemParamValidationError, When, }, world::{ FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut, @@ -455,9 +456,6 @@ macro_rules! impl_param_set_builder_tuple { system_meta .component_access_set .extend($meta.component_access_set); - system_meta - .archetype_component_access - .extend(&$meta.archetype_component_access); )* #[allow( clippy::unused_unit, @@ -471,7 +469,7 @@ macro_rules! impl_param_set_builder_tuple { all_tuples!(impl_param_set_builder_tuple, 1, 8, P, B, meta); -// SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts +// SAFETY: Relevant parameter ComponentId access is applied to SystemMeta. If any ParamState conflicts // with any prior access, a panic will occur. unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder

> SystemParamBuilder>> for ParamSetBuilder> @@ -495,9 +493,6 @@ unsafe impl<'w, 's, P: SystemParam, B: SystemParamBuilder

> system_meta .component_access_set .extend(meta.component_access_set); - system_meta - .archetype_component_access - .extend(&meta.archetype_component_access); } states } @@ -590,7 +585,7 @@ impl<'a> FilteredResourcesParamBuilder SystemParamBuilder> for FilteredResourcesParamBuilder @@ -615,15 +610,10 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)> if access.has_read_all_resources() { meta.component_access_set .add_unfiltered_read_all_resources(); - meta.archetype_component_access.read_all_resources(); } else { for component_id in access.resource_reads_and_writes() { meta.component_access_set .add_unfiltered_resource_read(component_id); - - let archetype_component_id = world.initialize_resource_internal(component_id).id(); - meta.archetype_component_access - .add_resource_read(archetype_component_id); } } @@ -654,7 +644,7 @@ impl<'a> FilteredResourcesMutParamBuilder SystemParamBuilder> for FilteredResourcesMutParamBuilder @@ -679,30 +669,20 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)> if access.has_read_all_resources() { meta.component_access_set .add_unfiltered_read_all_resources(); - meta.archetype_component_access.read_all_resources(); } else { for component_id in access.resource_reads() { meta.component_access_set .add_unfiltered_resource_read(component_id); - - let archetype_component_id = world.initialize_resource_internal(component_id).id(); - meta.archetype_component_access - .add_resource_read(archetype_component_id); } } if access.has_write_all_resources() { meta.component_access_set .add_unfiltered_write_all_resources(); - meta.archetype_component_access.write_all_resources(); } else { for component_id in access.resource_writes() { meta.component_access_set .add_unfiltered_resource_write(component_id); - - let archetype_component_id = world.initialize_resource_internal(component_id).id(); - meta.archetype_component_access - .add_resource_write(archetype_component_id); } } @@ -710,6 +690,49 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)> } } +/// A [`SystemParamBuilder`] for an [`Option`]. +#[derive(Clone)] +pub struct OptionBuilder(T); + +// SAFETY: `OptionBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `Option

` +unsafe impl> SystemParamBuilder> + for OptionBuilder +{ + fn build(self, world: &mut World, meta: &mut SystemMeta) -> as SystemParam>::State { + self.0.build(world, meta) + } +} + +/// A [`SystemParamBuilder`] for a [`Result`] of [`SystemParamValidationError`]. +#[derive(Clone)] +pub struct ResultBuilder(T); + +// SAFETY: `ResultBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `Result` +unsafe impl> + SystemParamBuilder> for ResultBuilder +{ + fn build( + self, + world: &mut World, + meta: &mut SystemMeta, + ) -> as SystemParam>::State { + self.0.build(world, meta) + } +} + +/// A [`SystemParamBuilder`] for a [`When`]. +#[derive(Clone)] +pub struct WhenBuilder(T); + +// SAFETY: `WhenBuilder` builds a state that is valid for `P`, and any state valid for `P` is valid for `When

(&self) - where - P: SystemParam, - { - self.param_warn_policy.try_warn::

(&self.name); - } - - /// Archetype component access that is used to determine which systems can run in parallel with each other - /// in the multithreaded executor. - /// - /// We use an [`ArchetypeComponentId`] as it is more precise than just checking [`ComponentId`]: - /// for example if you have one system with `Query<&mut A, With`, and one system with `Query<&mut A, Without`, - /// they conflict if you just look at the [`ComponentId`]; - /// but no archetype that matches the first query will match the second and vice versa, - /// which means there's no risk of conflict. - #[inline] - pub fn archetype_component_access(&self) -> &Access { - &self.archetype_component_access - } - - /// Returns a mutable reference to the [`Access`] for [`ArchetypeComponentId`]. - /// This is used to determine which systems can run in parallel with each other - /// in the multithreaded executor. - /// - /// We use an [`ArchetypeComponentId`] as it is more precise than just checking [`ComponentId`]: - /// for example if you have one system with `Query<&mut A, With`, and one system with `Query<&mut A, Without`, - /// they conflict if you just look at the [`ComponentId`]; - /// but no archetype that matches the first query will match the second and vice versa, - /// which means there's no risk of conflict. - /// - /// # Safety - /// - /// No access can be removed from the returned [`Access`]. - #[inline] - pub unsafe fn archetype_component_access_mut(&mut self) -> &mut Access { - &mut self.archetype_component_access - } - /// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`]. /// Used to check if systems and/or system params have conflicting access. #[inline] @@ -187,83 +121,6 @@ impl SystemMeta { } } -/// State machine for emitting warnings when [system params are invalid](System::validate_param). -#[derive(Clone, Copy)] -pub enum ParamWarnPolicy { - /// Stop app with a panic. - Panic, - /// No warning should ever be emitted. - Never, - /// The warning will be emitted once and status will update to [`Self::Never`]. - Warn, -} - -impl ParamWarnPolicy { - /// Advances the warn policy after validation failed. - #[inline] - fn advance(&mut self) { - // Ignore `Panic` case, because it stops execution before this function gets called. - *self = Self::Never; - } - - /// Emits a warning about inaccessible system param if policy allows it. - #[inline] - fn try_warn

(&self, name: &str) - where - P: SystemParam, - { - match self { - Self::Panic => panic!( - "{0} could not access system parameter {1}", - name, - disqualified::ShortName::of::

() - ), - Self::Warn => { - log::warn!( - "{0} did not run because it requested inaccessible system parameter {1}", - name, - disqualified::ShortName::of::

() - ); - } - Self::Never => {} - } - } -} - -/// Trait for manipulating warn policy of systems. -#[doc(hidden)] -pub trait WithParamWarnPolicy -where - M: 'static, - F: SystemParamFunction, - Self: Sized, -{ - /// Set warn policy. - fn with_param_warn_policy(self, warn_policy: ParamWarnPolicy) -> FunctionSystem; - - /// Warn and ignore systems with invalid parameters. - fn warn_param_missing(self) -> FunctionSystem { - self.with_param_warn_policy(ParamWarnPolicy::Warn) - } - - /// Silently ignore systems with invalid parameters. - fn ignore_param_missing(self) -> FunctionSystem { - self.with_param_warn_policy(ParamWarnPolicy::Never) - } -} - -impl WithParamWarnPolicy for F -where - M: 'static, - F: SystemParamFunction, -{ - fn with_param_warn_policy(self, param_warn_policy: ParamWarnPolicy) -> FunctionSystem { - let mut system = IntoSystem::into_system(self); - system.system_meta.set_param_warn_policy(param_warn_policy); - system - } -} - // TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference // (to avoid the need for unwrapping to retrieve SystemMeta) /// Holds on to persistent state required to drive [`SystemParam`] for a [`System`]. @@ -283,7 +140,7 @@ where /// [`SystemState`] values created can be cached to improve performance, /// and *must* be cached and reused in order for system parameters that rely on local state to work correctly. /// These include: -/// - [`Added`](crate::query::Added) and [`Changed`](crate::query::Changed) query filters +/// - [`Added`](crate::query::Added), [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) query filters /// - [`Local`](crate::system::Local) variables that hold state /// - [`EventReader`](crate::event::EventReader) system parameters, which rely on a [`Local`](crate::system::Local) to track which events have been seen /// @@ -360,7 +217,6 @@ pub struct SystemState { meta: SystemMeta, param_state: Param::State, world_id: WorldId, - archetype_generation: ArchetypeGeneration, } // Allow closure arguments to be inferred. @@ -418,12 +274,6 @@ all_tuples!( impl SystemState { /// Creates a new [`SystemState`] with default state. - /// - /// ## Note - /// For users of [`SystemState::get_manual`] or [`get_manual_mut`](SystemState::get_manual_mut): - /// - /// `new` does not cache any of the world's archetypes, so you must call [`SystemState::update_archetypes`] - /// manually before calling `get_manual{_mut}`. pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); meta.last_run = world.change_tick().relative_to(Tick::MAX); @@ -432,7 +282,6 @@ impl SystemState { meta, param_state, world_id: world.id(), - archetype_generation: ArchetypeGeneration::initial(), } } @@ -445,7 +294,6 @@ impl SystemState { meta, param_state, world_id: world.id(), - archetype_generation: ArchetypeGeneration::initial(), } } @@ -463,7 +311,6 @@ impl SystemState { world_id: self.world_id, }), system_meta: self.meta, - archetype_generation: self.archetype_generation, marker: PhantomData, } } @@ -487,19 +334,17 @@ impl SystemState { Param: ReadOnlySystemParam, { self.validate_world(world.id()); - self.update_archetypes(world); // SAFETY: Param is read-only and doesn't allow mutable access to World. // It also matches the World this SystemState was created with. - unsafe { self.get_unchecked_manual(world.as_unsafe_world_cell_readonly()) } + unsafe { self.get_unchecked(world.as_unsafe_world_cell_readonly()) } } /// Retrieve the mutable [`SystemParam`] values. #[inline] pub fn get_mut<'w, 's>(&'s mut self, world: &'w mut World) -> SystemParamItem<'w, 's, Param> { self.validate_world(world.id()); - self.update_archetypes(world); // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. - unsafe { self.get_unchecked_manual(world.as_unsafe_world_cell()) } + unsafe { self.get_unchecked(world.as_unsafe_world_cell()) } } /// Applies all state queued up for [`SystemParam`] values. For example, this will apply commands queued up @@ -515,11 +360,14 @@ impl SystemState { /// # Safety /// /// - The passed [`UnsafeWorldCell`] must have read-only access to - /// world data in `archetype_component_access`. + /// world data in `component_access_set`. /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - pub unsafe fn validate_param(state: &Self, world: UnsafeWorldCell) -> bool { + pub unsafe fn validate_param( + state: &mut Self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { // SAFETY: Delegated to existing `SystemParam` implementations. - unsafe { Param::validate_param(&state.param_state, &state.meta, world) } + unsafe { Param::validate_param(&mut state.param_state, &state.meta, world) } } /// Returns `true` if `world_id` matches the [`World`] that was used to call [`SystemState::new`]. @@ -545,89 +393,68 @@ impl SystemState { } } - /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before fetching the parameters, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if [`SystemState::get_manual`] or [`SystemState::get_manual_mut`] is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// [`SystemState::get`] or [`SystemState::get_mut`] do not need to call this as it will be automatically called for them. + /// Has no effect #[inline] - pub fn update_archetypes(&mut self, world: &World) { - self.update_archetypes_unsafe_world_cell(world.as_unsafe_world_cell_readonly()); - } + #[deprecated( + since = "0.17.0", + note = "No longer has any effect. Calls may be removed." + )] + pub fn update_archetypes(&mut self, _world: &World) {} - /// Updates the state's internal view of the `world`'s archetypes. If this is not called before fetching the parameters, - /// the results may not accurately reflect what is in the `world`. - /// - /// This is only required if [`SystemState::get_manual`] or [`SystemState::get_manual_mut`] is being called, and it only needs to - /// be called if the `world` has been structurally mutated (i.e. added/removed a component or resource). Users using - /// [`SystemState::get`] or [`SystemState::get_mut`] do not need to call this as it will be automatically called for them. - /// - /// # Note - /// - /// This method only accesses world metadata. + /// Has no effect #[inline] - pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { - assert_eq!(self.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); + #[deprecated( + since = "0.17.0", + note = "No longer has any effect. Calls may be removed." + )] + pub fn update_archetypes_unsafe_world_cell(&mut self, _world: UnsafeWorldCell) {} - let archetypes = world.archetypes(); - let old_generation = - core::mem::replace(&mut self.archetype_generation, archetypes.generation()); - - for archetype in &archetypes[old_generation..] { - // SAFETY: The assertion above ensures that the param_state was initialized from `world`. - unsafe { Param::new_archetype(&mut self.param_state, archetype, &mut self.meta) }; - } - } - - /// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only. - /// This will not update the state's view of the world's archetypes automatically nor increment the - /// world's change tick. - /// - /// For this to return accurate results, ensure [`SystemState::update_archetypes`] is called before this - /// function. - /// - /// Users should strongly prefer to use [`SystemState::get`] over this function. + /// Identical to [`SystemState::get`]. #[inline] + #[deprecated(since = "0.17.0", note = "Call `SystemState::get` instead.")] pub fn get_manual<'w, 's>(&'s mut self, world: &'w World) -> SystemParamItem<'w, 's, Param> where Param: ReadOnlySystemParam, { - self.validate_world(world.id()); - let change_tick = world.read_change_tick(); - // SAFETY: Param is read-only and doesn't allow mutable access to World. - // It also matches the World this SystemState was created with. - unsafe { self.fetch(world.as_unsafe_world_cell_readonly(), change_tick) } + self.get(world) } - /// Retrieve the mutable [`SystemParam`] values. This will not update the state's view of the world's archetypes - /// automatically nor increment the world's change tick. - /// - /// For this to return accurate results, ensure [`SystemState::update_archetypes`] is called before this - /// function. - /// - /// Users should strongly prefer to use [`SystemState::get_mut`] over this function. + /// Identical to [`SystemState::get_mut`]. #[inline] + #[deprecated(since = "0.17.0", note = "Call `SystemState::get_mut` instead.")] pub fn get_manual_mut<'w, 's>( &'s mut self, world: &'w mut World, ) -> SystemParamItem<'w, 's, Param> { - self.validate_world(world.id()); - let change_tick = world.change_tick(); - // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. - unsafe { self.fetch(world.as_unsafe_world_cell(), change_tick) } + self.get_mut(world) } - /// Retrieve the [`SystemParam`] values. This will not update archetypes automatically. + /// Identical to [`SystemState::get_unchecked`]. /// /// # Safety /// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data /// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was /// created with. #[inline] + #[deprecated(since = "0.17.0", note = "Call `SystemState::get_unchecked` instead.")] pub unsafe fn get_unchecked_manual<'w, 's>( &'s mut self, world: UnsafeWorldCell<'w>, + ) -> SystemParamItem<'w, 's, Param> { + // SAFETY: Caller ensures safety requirements + unsafe { self.get_unchecked(world) } + } + + /// Retrieve the [`SystemParam`] values. + /// + /// # Safety + /// This call might access any of the input parameters in a way that violates Rust's mutability rules. Make sure the data + /// access is safe in the context of global [`World`] access. The passed-in [`World`] _must_ be the [`World`] the [`SystemState`] was + /// created with. + #[inline] + pub unsafe fn get_unchecked<'w, 's>( + &'s mut self, + world: UnsafeWorldCell<'w>, ) -> SystemParamItem<'w, 's, Param> { let change_tick = world.increment_change_tick(); // SAFETY: The invariants are upheld by the caller. @@ -694,7 +521,6 @@ where func: F, state: Option>, system_meta: SystemMeta, - archetype_generation: ArchetypeGeneration, // NOTE: PhantomData T> gives this safe Send/Sync impls marker: PhantomData Marker>, } @@ -706,7 +532,7 @@ struct FunctionSystemState { /// The cached state of the system's [`SystemParam`]s. param: P::State, /// The id of the [`World`] this system was initialized with. If the world - /// passed to [`System::update_archetype_component_access`] does not match + /// passed to [`System::run_unsafe`] or [`System::validate_param_unsafe`] does not match /// this id, a panic will occur. world_id: WorldId, } @@ -734,7 +560,6 @@ where func: self.func.clone(), state: None, system_meta: SystemMeta::new::(), - archetype_generation: ArchetypeGeneration::initial(), marker: PhantomData, } } @@ -755,7 +580,6 @@ where func, state: None, system_meta: SystemMeta::new::(), - archetype_generation: ArchetypeGeneration::initial(), marker: PhantomData, } } @@ -791,8 +615,8 @@ where } #[inline] - fn archetype_component_access(&self) -> &Access { - &self.system_meta.archetype_component_access + fn component_access_set(&self) -> &FilteredAccessSet { + &self.system_meta.component_access_set } #[inline] @@ -821,14 +645,14 @@ where let change_tick = world.increment_change_tick(); - let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param; + let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); + assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); // SAFETY: - // - The caller has invoked `update_archetype_component_access`, which will panic - // if the world does not match. + // - The above assert ensures the world matches. // - All world accesses used by `F::Param` have been registered, so the caller // will ensure that there are no data access conflicts. let params = - unsafe { F::Param::get_param(param_state, &self.system_meta, world, change_tick) }; + unsafe { F::Param::get_param(&mut state.param, &self.system_meta, world, change_tick) }; let out = self.func.run(input, params); self.system_meta.last_run = change_tick; out @@ -847,18 +671,17 @@ where } #[inline] - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { - let param_state = &self.state.as_ref().expect(Self::ERROR_UNINITIALIZED).param; + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); + assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); // SAFETY: - // - The caller has invoked `update_archetype_component_access`, which will panic - // if the world does not match. + // - The above assert ensures the world matches. // - All world accesses used by `F::Param` have been registered, so the caller // will ensure that there are no data access conflicts. - let is_valid = unsafe { F::Param::validate_param(param_state, &self.system_meta, world) }; - if !is_valid { - self.system_meta.advance_param_warn_policy(); - } - is_valid + unsafe { F::Param::validate_param(&mut state.param, &self.system_meta, world) } } #[inline] @@ -878,20 +701,6 @@ where self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); } - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - let state = self.state.as_mut().expect(Self::ERROR_UNINITIALIZED); - assert_eq!(state.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); - - let archetypes = world.archetypes(); - let old_generation = - core::mem::replace(&mut self.archetype_generation, archetypes.generation()); - - for archetype in &archetypes[old_generation..] { - // SAFETY: The assertion above ensures that the param_state was initialized from `world`. - unsafe { F::Param::new_archetype(&mut state.param, archetype, &mut self.system_meta) }; - } - } - #[inline] fn check_change_tick(&mut self, change_tick: Tick) { check_system_change_tick( @@ -1070,6 +879,7 @@ macro_rules! impl_system_function { #[inline] fn run(&mut self, input: In::Inner<'_>, param_value: SystemParamItem< ($($param,)*)>) -> Out { fn call_inner( + _: PhantomData, mut f: impl FnMut(In::Param<'_>, $($param,)*)->Out, input: In::Inner<'_>, $($param: $param,)* @@ -1077,7 +887,7 @@ macro_rules! impl_system_function { f(In::wrap(input), $($param,)*) } let ($($param,)*) = param_value; - call_inner(self, input, $($param),*) + call_inner(PhantomData::, self, input, $($param),*) } } }; diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index fbb4a458d5..c3448fb819 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -153,7 +153,7 @@ pub use system_name::*; pub use system_param::*; pub use system_registry::*; -use crate::world::World; +use crate::world::{FromWorld, World}; /// Conversion trait to turn something into a [`System`]. /// @@ -228,6 +228,77 @@ pub trait IntoSystem: Sized { IntoAdapterSystem::new(f, self) } + /// Passes a mutable reference to `value` as input to the system each run, + /// turning it into a system that takes no input. + /// + /// `Self` can have any [`SystemInput`] type that takes a mutable reference + /// to `T`, such as [`InMut`]. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// fn my_system(InMut(value): InMut) { + /// *value += 1; + /// if *value > 10 { + /// println!("Value is greater than 10!"); + /// } + /// } + /// + /// # let mut schedule = Schedule::default(); + /// schedule.add_systems(my_system.with_input(0)); + /// # bevy_ecs::system::assert_is_system(my_system.with_input(0)); + /// ``` + fn with_input(self, value: T) -> WithInputWrapper + where + for<'i> In: SystemInput = &'i mut T>, + T: Send + Sync + 'static, + { + WithInputWrapper::new(self, value) + } + + /// Passes a mutable reference to a value of type `T` created via + /// [`FromWorld`] as input to the system each run, turning it into a system + /// that takes no input. + /// + /// `Self` can have any [`SystemInput`] type that takes a mutable reference + /// to `T`, such as [`InMut`]. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// struct MyData { + /// value: usize, + /// } + /// + /// impl FromWorld for MyData { + /// fn from_world(world: &mut World) -> Self { + /// // Fetch from the world the data needed to create `MyData` + /// # MyData { value: 0 } + /// } + /// } + /// + /// fn my_system(InMut(data): InMut) { + /// data.value += 1; + /// if data.value > 10 { + /// println!("Value is greater than 10!"); + /// } + /// } + /// # let mut schedule = Schedule::default(); + /// schedule.add_systems(my_system.with_input_from::()); + /// # bevy_ecs::system::assert_is_system(my_system.with_input_from::()); + /// ``` + fn with_input_from(self) -> WithInputFromWrapper + where + for<'i> In: SystemInput = &'i mut T>, + T: FromWorld + Send + Sync + 'static, + { + WithInputFromWrapper::new(self) + } + /// Get the [`TypeId`] of the [`System`] produced after calling [`into_system`](`IntoSystem::into_system`). #[inline] fn system_type_id(&self) -> TypeId { @@ -331,25 +402,26 @@ mod tests { use std::println; use crate::{ - archetype::{ArchetypeComponentId, Archetypes}, + archetype::Archetypes, bundle::Bundles, change_detection::DetectChanges, component::{Component, Components}, entity::{Entities, Entity}, error::Result, - prelude::{AnyOf, EntityRef}, - query::{Added, Changed, Or, With, Without}, + name::Name, + prelude::{AnyOf, EntityRef, Trigger}, + query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without}, removal_detection::RemovedComponents, resource::Resource, schedule::{ - common_conditions::resource_exists, ApplyDeferred, Condition, IntoScheduleConfigs, - Schedule, + common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule, + SystemCondition, }, system::{ - Commands, In, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res, ResMut, - Single, StaticSystemParam, System, SystemState, + Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res, + ResMut, Single, StaticSystemParam, System, SystemState, }, - world::{DeferredWorld, EntityMut, FromWorld, World}, + world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World}, }; use super::ScheduleSystem; @@ -1254,6 +1326,25 @@ mod tests { } } + #[test] + fn system_state_spawned() { + let mut world = World::default(); + world.spawn_empty(); + let spawn_tick = world.change_tick(); + + let mut system_state: SystemState>> = + SystemState::new(&mut world); + { + let query = system_state.get(&world); + assert_eq!(query.unwrap().spawned_at(), spawn_tick); + } + + { + let query = system_state.get(&world); + assert!(query.is_none()); + } + } + #[test] #[should_panic] fn system_state_invalid_world() { @@ -1479,68 +1570,21 @@ mod tests { let mut sys = IntoSystem::into_system(mutable_query); sys.initialize(&mut world); } - } - #[test] - fn update_archetype_component_access_works() { - use std::collections::HashSet; + { + let mut world = World::new(); - fn a_not_b_system(_query: Query<&A, Without>) {} + fn mutable_query(mut query: Query<(&mut A, &mut B, SpawnDetails), Spawned>) { + for _ in &mut query {} - let mut world = World::default(); - let mut system = IntoSystem::into_system(a_not_b_system); - let mut expected_ids = HashSet::::new(); - let a_id = world.register_component::(); + immutable_query(query.as_readonly()); + } - // set up system and verify its access is empty - system.initialize(&mut world); - system.update_archetype_component_access(world.as_unsafe_world_cell()); - let archetype_component_access = system.archetype_component_access(); - assert!(expected_ids - .iter() - .all(|id| archetype_component_access.has_component_read(*id))); + fn immutable_query(_: Query<(&A, &B, SpawnDetails), Spawned>) {} - // add some entities with archetypes that should match and save their ids - expected_ids.insert( - world - .spawn(A) - .archetype() - .get_archetype_component_id(a_id) - .unwrap(), - ); - expected_ids.insert( - world - .spawn((A, C)) - .archetype() - .get_archetype_component_id(a_id) - .unwrap(), - ); - - // add some entities with archetypes that should not match - world.spawn((A, B)); - world.spawn((B, C)); - - // update system and verify its accesses are correct - system.update_archetype_component_access(world.as_unsafe_world_cell()); - let archetype_component_access = system.archetype_component_access(); - assert!(expected_ids - .iter() - .all(|id| archetype_component_access.has_component_read(*id))); - - // one more round - expected_ids.insert( - world - .spawn((A, D)) - .archetype() - .get_archetype_component_id(a_id) - .unwrap(), - ); - world.spawn((A, B, D)); - system.update_archetype_component_access(world.as_unsafe_world_cell()); - let archetype_component_access = system.archetype_component_access(); - assert!(expected_ids - .iter() - .all(|id| archetype_component_access.has_component_read(*id))); + let mut sys = IntoSystem::into_system(mutable_query); + sys.initialize(&mut world); + } } #[test] @@ -1823,4 +1867,95 @@ mod tests { let mut world = World::new(); run_system(&mut world, sys); } + + // Regression test for + // https://github.com/bevyengine/bevy/issues/18778 + // + // Dear rustc team, please reach out if you encounter this + // in a crater run and we can work something out! + // + // These todo! macro calls should never be removed; + // they're intended to demonstrate real-world usage + // in a way that's clearer than simply calling `panic!` + // + // Because type inference behaves differently for functions and closures, + // we need to test both, in addition to explicitly annotating the return type + // to ensure that there are no upstream regressions there. + #[test] + fn nondiverging_never_trait_impls() { + // This test is a compilation test: + // no meaningful logic is ever actually evaluated. + // It is simply intended to check that the correct traits are implemented + // when todo! or similar nondiverging panics are used. + let mut world = World::new(); + let mut schedule = Schedule::default(); + + fn sys(_query: Query<&Name>) { + todo!() + } + + schedule.add_systems(sys); + schedule.add_systems(|_query: Query<&Name>| {}); + schedule.add_systems(|_query: Query<&Name>| todo!()); + #[expect(clippy::unused_unit, reason = "this forces the () return type")] + schedule.add_systems(|_query: Query<&Name>| -> () { todo!() }); + + fn obs(_trigger: Trigger) { + todo!() + } + + world.add_observer(obs); + world.add_observer(|_trigger: Trigger| {}); + world.add_observer(|_trigger: Trigger| todo!()); + #[expect(clippy::unused_unit, reason = "this forces the () return type")] + world.add_observer(|_trigger: Trigger| -> () { todo!() }); + + fn my_command(_world: &mut World) { + todo!() + } + + world.commands().queue(my_command); + world.commands().queue(|_world: &mut World| {}); + world.commands().queue(|_world: &mut World| todo!()); + #[expect(clippy::unused_unit, reason = "this forces the () return type")] + world + .commands() + .queue(|_world: &mut World| -> () { todo!() }); + } + + #[test] + fn with_input() { + fn sys(InMut(v): InMut) { + *v += 1; + } + + let mut world = World::new(); + let mut system = IntoSystem::into_system(sys.with_input(42)); + system.initialize(&mut world); + system.run((), &mut world); + assert_eq!(*system.value(), 43); + } + + #[test] + fn with_input_from() { + struct TestData(usize); + + impl FromWorld for TestData { + fn from_world(_world: &mut World) -> Self { + Self(5) + } + } + + fn sys(InMut(v): InMut) { + v.0 += 1; + } + + let mut world = World::new(); + let mut system = IntoSystem::into_system(sys.with_input_from::()); + assert!(system.value().is_none()); + system.initialize(&mut world); + assert!(system.value().is_some()); + system.run((), &mut world); + assert_eq!(system.value().unwrap().0, 6); + } } diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index 17dd4fb017..d3138151c9 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -2,17 +2,17 @@ use alloc::{borrow::Cow, vec::Vec}; use core::marker::PhantomData; use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, error::Result, + never::Never, prelude::{Bundle, Trigger}, - query::Access, + query::{Access, FilteredAccessSet}, schedule::{Fallible, Infallible}, system::{input::SystemIn, System}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, }; -use super::IntoSystem; +use super::{IntoSystem, SystemParamValidationError}; /// Implemented for [`System`]s that have a [`Trigger`] as the first argument. pub trait ObserverSystem: @@ -45,7 +45,7 @@ pub trait IntoObserverSystem: Send + 'st fn into_system(this: Self) -> Self::System; } -impl IntoObserverSystem for S +impl IntoObserverSystem for S where S: IntoSystem, Out, M> + Send + 'static, S::System: ObserverSystem, @@ -66,7 +66,19 @@ where E: Send + Sync + 'static, B: Bundle, { - type System = InfallibleObserverWrapper; + type System = InfallibleObserverWrapper; + + fn into_system(this: Self) -> Self::System { + InfallibleObserverWrapper::new(IntoSystem::into_system(this)) + } +} +impl IntoObserverSystem for S +where + S: IntoSystem, Never, M> + Send + 'static, + E: Send + Sync + 'static, + B: Bundle, +{ + type System = InfallibleObserverWrapper; fn into_system(this: Self) -> Self::System { InfallibleObserverWrapper::new(IntoSystem::into_system(this)) @@ -74,12 +86,12 @@ where } /// A wrapper that converts an observer system that returns `()` into one that returns `Ok(())`. -pub struct InfallibleObserverWrapper { +pub struct InfallibleObserverWrapper { observer: S, - _marker: PhantomData<(E, B)>, + _marker: PhantomData<(E, B, Out)>, } -impl InfallibleObserverWrapper { +impl InfallibleObserverWrapper { /// Create a new `InfallibleObserverWrapper`. pub fn new(observer: S) -> Self { Self { @@ -89,11 +101,12 @@ impl InfallibleObserverWrapper { } } -impl System for InfallibleObserverWrapper +impl System for InfallibleObserverWrapper where - S: ObserverSystem, + S: ObserverSystem, E: Send + Sync + 'static, B: Bundle, + Out: Send + Sync + 'static, { type In = Trigger<'static, E, B>; type Out = Result; @@ -109,8 +122,8 @@ where } #[inline] - fn archetype_component_access(&self) -> &Access { - self.observer.archetype_component_access() + fn component_access_set(&self) -> &FilteredAccessSet { + self.observer.component_access_set() } #[inline] @@ -138,12 +151,6 @@ where Ok(()) } - #[inline] - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { - self.observer.run(input, world); - Ok(()) - } - #[inline] fn apply_deferred(&mut self, world: &mut World) { self.observer.apply_deferred(world); @@ -155,7 +162,10 @@ where } #[inline] - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { self.observer.validate_param_unsafe(world) } @@ -164,11 +174,6 @@ where self.observer.initialize(world); } - #[inline] - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - self.observer.update_archetype_component_access(world); - } - #[inline] fn check_change_tick(&mut self, change_tick: Tick) { self.observer.check_change_tick(change_tick); diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 2f21e4dec9..c67bf7b337 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,9 +1,7 @@ use crate::{ batching::BatchingStrategy, component::Tick, - entity::{ - unique_array::UniqueEntityArray, Entity, EntityBorrow, EntityDoesNotExistError, EntitySet, - }, + entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, query::{ DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, @@ -17,33 +15,40 @@ use core::{ ops::{Deref, DerefMut}, }; -/// [System parameter] that provides selective access to the [`Component`] data stored in a [`World`]. +/// A [system parameter] that provides selective access to the [`Component`] data stored in a [`World`]. /// -/// Enables access to [entity identifiers] and [components] from a system, without the need to directly access the world. -/// Its iterators and getter methods return *query items*. -/// Each query item is a type containing data relative to an entity. +/// Queries enable systems to access [entity identifiers] and [components] without requiring direct access to the [`World`]. +/// Its iterators and getter methods return *query items*, which are types containing data related to an entity. /// /// `Query` is a generic data structure that accepts two type parameters: /// -/// - **`D` (query data).** -/// The type of data contained in the query item. +/// - **`D` (query data)**: +/// The type of data fetched by the query, which will be returned as the query item. /// Only entities that match the requested data will generate an item. /// Must implement the [`QueryData`] trait. -/// - **`F` (query filter).** -/// A set of conditions that determines whether query items should be kept or discarded. +/// - **`F` (query filter)**: +/// An optional set of conditions that determine whether query items should be kept or discarded. +/// This defaults to [`unit`], which means no additional filters will be applied. /// Must implement the [`QueryFilter`] trait. -/// This type parameter is optional. /// +/// [system parameter]: crate::system::SystemParam +/// [`Component`]: crate::component::Component /// [`World`]: crate::world::World +/// [entity identifiers]: Entity +/// [components]: crate::component::Component /// /// # Similar parameters /// -/// [`Query`] has few sibling [`SystemParam`](crate::system::system_param::SystemParam)s, which perform additional validation: +/// `Query` has few sibling [`SystemParam`]s, which perform additional validation: +/// /// - [`Single`] - Exactly one matching query item. /// - [`Option`] - Zero or one matching query item. /// - [`Populated`] - At least one matching query item. /// -/// Those parameters will prevent systems from running if their requirements aren't met. +/// These parameters will prevent systems from running if their requirements are not met. +/// +/// [`SystemParam`]: crate::system::system_param::SystemParam +/// [`Option`]: Single /// /// # System parameter declaration /// @@ -52,330 +57,429 @@ use core::{ /// /// ## Component access /// -/// A query defined with a reference to a component as the query fetch type parameter can be used to generate items that refer to the data of said component. +/// You can fetch an entity's component by specifying a reference to that component in the query's data parameter: /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; -/// # fn immutable_ref( -/// // A component can be accessed by shared reference... -/// query: Query<&ComponentA> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(immutable_ref); +/// # +/// // A component can be accessed by a shared reference... +/// fn immutable_query(query: Query<&ComponentA>) { +/// // ... +/// } /// -/// # fn mutable_ref( -/// // ... or by mutable reference. -/// query: Query<&mut ComponentA> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(mutable_ref); +/// // ...or by a mutable reference. +/// fn mutable_query(query: Query<&mut ComponentA>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(immutable_query); +/// # bevy_ecs::system::assert_is_system(mutable_query); +/// ``` +/// +/// Note that components need to be behind a reference (`&` or `&mut`), or the query will not compile: +/// +/// ```compile_fail,E0277 +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct ComponentA; +/// # +/// // This needs to be `&ComponentA` or `&mut ComponentA` in order to compile. +/// fn invalid_query(query: Query) { +/// // ... +/// } /// ``` /// /// ## Query filtering /// -/// Setting the query filter type parameter will ensure that each query item satisfies the given condition. +/// Setting the query filter type parameter will ensure that each query item satisfies the given condition: /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; +/// # /// # #[derive(Component)] /// # struct ComponentB; -/// # fn system( -/// // Just `ComponentA` data will be accessed, but only for entities that also contain -/// // `ComponentB`. -/// query: Query<&ComponentA, With> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(system); +/// # +/// // `ComponentA` data will be accessed, but only for entities that also contain `ComponentB`. +/// fn filtered_query(query: Query<&ComponentA, With>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(filtered_query); /// ``` /// +/// Note that the filter is `With`, not `With<&ComponentB>`. Unlike query data, `With` +/// does not require components to be behind a reference. +/// /// ## `QueryData` or `QueryFilter` tuples /// -/// Using tuples, each `Query` type parameter can contain multiple elements. +/// Using [`tuple`]s, each `Query` type parameter can contain multiple elements. /// -/// In the following example, two components are accessed simultaneously, and the query items are filtered on two conditions. +/// In the following example two components are accessed simultaneously, and the query items are +/// filtered on two conditions: /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; +/// # /// # #[derive(Component)] /// # struct ComponentB; +/// # /// # #[derive(Component)] /// # struct ComponentC; +/// # /// # #[derive(Component)] /// # struct ComponentD; -/// # fn immutable_ref( -/// query: Query<(&ComponentA, &ComponentB), (With, Without)> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(immutable_ref); +/// # +/// fn complex_query( +/// query: Query<(&mut ComponentA, &ComponentB), (With, Without)> +/// ) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(complex_query); +/// ``` +/// +/// Note that this currently only works on tuples with 15 or fewer items. You may nest tuples to +/// get around this limit: +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct ComponentA; +/// # +/// # #[derive(Component)] +/// # struct ComponentB; +/// # +/// # #[derive(Component)] +/// # struct ComponentC; +/// # +/// # #[derive(Component)] +/// # struct ComponentD; +/// # +/// fn nested_query( +/// query: Query<(&ComponentA, &ComponentB, (&mut ComponentC, &mut ComponentD))> +/// ) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(nested_query); /// ``` /// /// ## Entity identifier access /// -/// The identifier of an entity can be made available inside the query item by including [`Entity`] in the query fetch type parameter. +/// You can access [`Entity`], the entity identifier, by including it in the query data parameter: /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; -/// # fn system( -/// query: Query<(Entity, &ComponentA)> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(system); +/// # +/// fn entity_id_query(query: Query<(Entity, &ComponentA)>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(entity_id_query); /// ``` /// +/// Be aware that [`Entity`] is not a component, so it does not need to be behind a reference. +/// /// ## Optional component access /// -/// A component can be made optional in a query by wrapping it into an [`Option`]. -/// In this way, a query item can still be generated even if the queried entity does not contain the wrapped component. -/// In this case, its corresponding value will be `None`. +/// A component can be made optional by wrapping it into an [`Option`]. In the following example, a +/// query item will still be generated even if the queried entity does not contain `ComponentB`. +/// When this is the case, `Option<&ComponentB>`'s corresponding value will be `None`. /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; +/// # /// # #[derive(Component)] /// # struct ComponentB; -/// # fn system( -/// // Generates items for entities that contain `ComponentA`, and optionally `ComponentB`. -/// query: Query<(&ComponentA, Option<&ComponentB>)> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(system); +/// # +/// // Queried items must contain `ComponentA`. If they also contain `ComponentB`, its value will +/// // be fetched as well. +/// fn optional_component_query(query: Query<(&ComponentA, Option<&ComponentB>)>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(optional_component_query); /// ``` /// -/// See the documentation for [`AnyOf`] to idiomatically declare many optional components. +/// Optional components can hurt performance in some cases, so please read the [performance] +/// section to learn more about them. Additionally, if you need to declare several optional +/// components, you may be interested in using [`AnyOf`]. /// -/// See the [performance] section to learn more about the impact of optional components. +/// [performance]: #performance +/// [`AnyOf`]: crate::query::AnyOf /// /// ## Disjoint queries /// -/// A system cannot contain two queries that break Rust's mutability rules. -/// In this case, the [`Without`] filter can be used to disjoint them. +/// A system cannot contain two queries that break Rust's mutability rules, or else it will panic +/// when initialized. This can often be fixed with the [`Without`] filter, which makes the queries +/// disjoint. /// -/// In the following example, two queries mutably access the same component. -/// Executing this system will panic, since an entity could potentially match the two queries at the same time by having both `Player` and `Enemy` components. -/// This would violate mutability rules. +/// In the following example, the two queries can mutably access the same `&mut Health` component +/// if an entity has both the `Player` and `Enemy` components. Bevy will catch this and panic, +/// however, instead of breaking Rust's mutability rules: /// /// ```should_panic /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct Health; +/// # /// # #[derive(Component)] /// # struct Player; +/// # /// # #[derive(Component)] /// # struct Enemy; /// # /// fn randomize_health( /// player_query: Query<&mut Health, With>, /// enemy_query: Query<&mut Health, With>, -/// ) -/// # {} -/// # let mut randomize_health_system = IntoSystem::into_system(randomize_health); -/// # let mut world = World::new(); -/// # randomize_health_system.initialize(&mut world); -/// # randomize_health_system.run((), &mut world); +/// ) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_system_does_not_conflict(randomize_health); /// ``` /// -/// Adding a `Without` filter will disjoint the queries. -/// In this way, any entity that has both `Player` and `Enemy` components is excluded from both queries. +/// Adding a [`Without`] filter will disjoint the queries. In the following example, any entity +/// that has both the `Player` and `Enemy` components will be excluded from _both_ queries: /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct Health; +/// # /// # #[derive(Component)] /// # struct Player; +/// # /// # #[derive(Component)] /// # struct Enemy; /// # /// fn randomize_health( /// player_query: Query<&mut Health, (With, Without)>, /// enemy_query: Query<&mut Health, (With, Without)>, -/// ) -/// # {} -/// # let mut randomize_health_system = IntoSystem::into_system(randomize_health); -/// # let mut world = World::new(); -/// # randomize_health_system.initialize(&mut world); -/// # randomize_health_system.run((), &mut world); +/// ) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_system_does_not_conflict(randomize_health); /// ``` /// -/// An alternative to this idiom is to wrap the conflicting queries into a [`ParamSet`](super::ParamSet). +/// An alternative solution to this problem would be to wrap the conflicting queries in +/// [`ParamSet`]. +/// +/// [`Without`]: crate::query::Without +/// [`ParamSet`]: crate::system::ParamSet /// /// ## Whole Entity Access /// -/// [`EntityRef`]s can be fetched from a query. This will give read-only access to any component on the entity, -/// and can be used to dynamically fetch any component without baking it into the query type. Due to this global -/// access to the entity, this will block any other system from parallelizing with it. As such these queries -/// should be sparingly used. +/// [`EntityRef`] can be used in a query to gain read-only access to all components of an entity. +/// This is useful when dynamically fetching components instead of baking them into the query type. /// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; -/// # fn system( -/// query: Query<(EntityRef, &ComponentA)> -/// # ) {} -/// # bevy_ecs::system::assert_is_system(system); +/// # +/// fn all_components_query(query: Query<(EntityRef, &ComponentA)>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_is_system(all_components_query); /// ``` /// -/// As `EntityRef` can read any component on an entity, a query using it will conflict with *any* mutable -/// access. It is strongly advised to couple `EntityRef` queries with the use of either `With`/`Without` -/// filters or `ParamSets`. This also limits the scope of the query, which will improve iteration performance -/// and also allows it to parallelize with other non-conflicting systems. +/// As [`EntityRef`] can read any component on an entity, a query using it will conflict with *any* +/// mutable component access. /// /// ```should_panic /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; -/// # fn system( -/// // This will panic! -/// // EntityRef provides read access to ALL components on an entity. -/// // When combined with &mut ComponentA in the same query, it creates -/// // a conflict because EntityRef could read ComponentA while the &mut -/// // attempts to modify it - violating Rust's borrowing rules of no -/// // simultaneous read+write access. -/// query: Query<(EntityRef, &mut ComponentA)> -/// # ) {} -/// # bevy_ecs::system::assert_system_does_not_conflict(system); +/// # +/// // `EntityRef` provides read access to *all* components on an entity. When combined with +/// // `&mut ComponentA` in the same query, it creates a conflict because `EntityRef` could read +/// // `&ComponentA` while `&mut ComponentA` attempts to modify it - violating Rust's borrowing +/// // rules. +/// fn invalid_query(query: Query<(EntityRef, &mut ComponentA)>) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_system_does_not_conflict(invalid_query); /// ``` +/// +/// It is strongly advised to couple [`EntityRef`] queries with the use of either [`With`] / +/// [`Without`] filters or [`ParamSet`]s. Not only does this improve the performance and +/// parallelization of the system, but it enables systems to gain mutable access to other +/// components: +/// /// ``` /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; +/// # /// # #[derive(Component)] /// # struct ComponentB; -/// # fn system( -/// // This will not panic. -/// // This creates a perfect separation where: -/// // 1. First query reads entities that have ComponentA -/// // 2. Second query modifies ComponentB only on entities that DON'T have ComponentA -/// // Result: No entity can ever be accessed by both queries simultaneously -/// query_a: Query>, -/// query_b: Query<&mut ComponentB, Without>, -/// # ) {} -/// # bevy_ecs::system::assert_system_does_not_conflict(system); +/// # +/// // The first query only reads entities that have `ComponentA`, while the second query only +/// // modifies entities that *don't* have `ComponentA`. Because neither query will access the same +/// // entity, this system does not conflict. +/// fn disjoint_query( +/// query_a: Query>, +/// query_b: Query<&mut ComponentB, Without>, +/// ) { +/// // ... +/// } +/// # +/// # bevy_ecs::system::assert_system_does_not_conflict(disjoint_query); /// ``` +/// /// The fundamental rule: [`EntityRef`]'s ability to read all components means it can never -/// coexist with mutable access. With/Without filters guarantee this by keeping the +/// coexist with mutable access. [`With`] / [`Without`] filters can guarantee this by keeping the /// queries on completely separate entities. /// +/// [`EntityRef`]: crate::world::EntityRef +/// [`With`]: crate::query::With +/// /// # Accessing query items /// -/// The following table summarizes the behavior of the safe methods that can be used to get query items. +/// The following table summarizes the behavior of safe methods that can be used to get query +/// items: /// /// |Query methods|Effect| -/// |:---:|---| -/// |[`iter`]\[[`_mut`][`iter_mut`]]|Returns an iterator over all query items.| -/// |[[`iter().for_each()`][`for_each`]\[[`iter_mut().for_each()`][`for_each`]],
[`par_iter`]\[[`_mut`][`par_iter_mut`]]|Runs a specified function for each query item.| -/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|Iterates or runs a specified function over query items generated by a list of entities.| -/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|Returns an iterator over all combinations of a specified number of query items.| -/// |[`get`]\[[`_mut`][`get_mut`]]|Returns the query item for the specified entity.| -/// |[`many`]\[[`_mut`][`many_mut`]],
[`get_many`]\[[`_mut`][`get_many_mut`]]|Returns the query items for the specified entities.| -/// |[`single`]\[[`_mut`][`single_mut`]],
[`single`]\[[`_mut`][`single_mut`]]|Returns the query item while verifying that there aren't others.| +/// |-|-| +/// |[`iter`]\[[`_mut`][`iter_mut`]\]|Returns an iterator over all query items.| +/// |[`iter[_mut]().for_each()`][`for_each`],
[`par_iter`]\[[`_mut`][`par_iter_mut`]\]|Runs a specified function for each query item.| +/// |[`iter_many`]\[[`_unique`][`iter_many_unique`]\]\[[`_mut`][`iter_many_mut`]\]|Iterates over query items that match a list of entities.| +/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]\]|Iterates over all combinations of query items.| +/// |[`single`](Self::single)\[[`_mut`][`single_mut`]\]|Returns a single query item if only one exists.| +/// |[`get`]\[[`_mut`][`get_mut`]\]|Returns the query item for a specified entity.| +/// |[`get_many`]\[[`_unique`][`get_many_unique`]\]\[[`_mut`][`get_many_mut`]\]|Returns all query items that match a list of entities.| /// /// There are two methods for each type of query operation: immutable and mutable (ending with `_mut`). /// When using immutable methods, the query items returned are of type [`ROQueryItem`], a read-only version of the query item. /// In this circumstance, every mutable reference in the query fetch type parameter is substituted by a shared reference. /// +/// [`iter`]: Self::iter +/// [`iter_mut`]: Self::iter_mut +/// [`for_each`]: #iteratorfor_each +/// [`par_iter`]: Self::par_iter +/// [`par_iter_mut`]: Self::par_iter_mut +/// [`iter_many`]: Self::iter_many +/// [`iter_many_unique`]: Self::iter_many_unique +/// [`iter_many_mut`]: Self::iter_many_mut +/// [`iter_combinations`]: Self::iter_combinations +/// [`iter_combinations_mut`]: Self::iter_combinations_mut +/// [`single_mut`]: Self::single_mut +/// [`get`]: Self::get +/// [`get_mut`]: Self::get_mut +/// [`get_many`]: Self::get_many +/// [`get_many_unique`]: Self::get_many_unique +/// [`get_many_mut`]: Self::get_many_mut +/// /// # Performance /// -/// Creating a `Query` is a low-cost constant operation. -/// Iterating it, on the other hand, fetches data from the world and generates items, which can have a significant computational cost. +/// Creating a `Query` is a low-cost constant operation. Iterating it, on the other hand, fetches +/// data from the world and generates items, which can have a significant computational cost. /// -/// [`Table`] component storage type is much more optimized for query iteration than [`SparseSet`]. +/// Two systems cannot be executed in parallel if both access the same component type where at +/// least one of the accesses is mutable. Because of this, it is recommended for queries to only +/// fetch mutable access to components when necessary, since immutable access can be parallelized. /// -/// Two systems cannot be executed in parallel if both access the same component type where at least one of the accesses is mutable. -/// This happens unless the executor can verify that no entity could be found in both queries. +/// Query filters ([`With`] / [`Without`]) can improve performance because they narrow the kinds of +/// entities that can be fetched. Systems that access fewer kinds of entities are more likely to be +/// parallelized by the scheduler. /// -/// Optional components increase the number of entities a query has to match against. -/// This can hurt iteration performance, especially if the query solely consists of only optional components, since the query would iterate over each entity in the world. +/// On the other hand, be careful using optional components (`Option<&ComponentA>`) and +/// [`EntityRef`] because they broaden the amount of entities kinds that can be accessed. This is +/// especially true of a query that _only_ fetches optional components or [`EntityRef`], as the +/// query would iterate over all entities in the world. /// -/// The following table compares the computational complexity of the various methods and operations, where: +/// There are two types of [component storage types]: [`Table`] and [`SparseSet`]. [`Table`] offers +/// fast iteration speeds, but slower insertion and removal speeds. [`SparseSet`] is the opposite: +/// it offers fast component insertion and removal speeds, but slower iteration speeds. /// -/// - **n** is the number of entities that match the query, -/// - **r** is the number of elements in a combination, -/// - **k** is the number of involved entities in the operation, -/// - **a** is the number of archetypes in the world, -/// - **C** is the [binomial coefficient], used to count combinations. -/// nCr is read as "*n* choose *r*" and is equivalent to the number of distinct unordered subsets of *r* elements that can be taken from a set of *n* elements. +/// The following table compares the computational complexity of the various methods and +/// operations, where: +/// +/// - **n** is the number of entities that match the query. +/// - **r** is the number of elements in a combination. +/// - **k** is the number of involved entities in the operation. +/// - **a** is the number of archetypes in the world. +/// - **C** is the [binomial coefficient], used to count combinations. nCr is +/// read as "*n* choose *r*" and is equivalent to the number of distinct unordered subsets of *r* +/// elements that can be taken from a set of *n* elements. /// /// |Query operation|Computational complexity| -/// |:---:|:---:| -/// |[`iter`]\[[`_mut`][`iter_mut`]]|O(n)| -/// |[[`iter().for_each()`][`for_each`]\[[`iter_mut().for_each()`][`for_each`]],
[`par_iter`]\[[`_mut`][`par_iter_mut`]]|O(n)| -/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]]|O(k)| -/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]]|O(nCr)| -/// |[`get`]\[[`_mut`][`get_mut`]]|O(1)| -/// |([`get_`][`get_many`])[`many`]|O(k)| -/// |([`get_`][`get_many_mut`])[`many_mut`]|O(k2)| -/// |[`single`]\[[`_mut`][`single_mut`]],
[`single`]\[[`_mut`][`single_mut`]]|O(a)| -/// |Archetype based filtering ([`With`], [`Without`], [`Or`])|O(a)| -/// |Change detection filtering ([`Added`], [`Changed`])|O(a + n)| +/// |-|-| +/// |[`iter`]\[[`_mut`][`iter_mut`]\]|O(n)| +/// |[`iter[_mut]().for_each()`][`for_each`],
[`par_iter`]\[[`_mut`][`par_iter_mut`]\]|O(n)| +/// |[`iter_many`]\[[`_mut`][`iter_many_mut`]\]|O(k)| +/// |[`iter_combinations`]\[[`_mut`][`iter_combinations_mut`]\]|O(nCr)| +/// |[`single`](Self::single)\[[`_mut`][`single_mut`]\]|O(a)| +/// |[`get`]\[[`_mut`][`get_mut`]\]|O(1)| +/// |[`get_many`]|O(k)| +/// |[`get_many_mut`]|O(k2)| +/// |Archetype-based filtering ([`With`], [`Without`], [`Or`])|O(a)| +/// |Change detection filtering ([`Added`], [`Changed`], [`Spawned`])|O(a + n)| +/// +/// [component storage types]: crate::component::StorageType +/// [`Table`]: crate::storage::Table +/// [`SparseSet`]: crate::storage::SparseSet +/// [binomial coefficient]: https://en.wikipedia.org/wiki/Binomial_coefficient +/// [`Or`]: crate::query::Or +/// [`Added`]: crate::query::Added +/// [`Changed`]: crate::query::Changed +/// [`Spawned`]: crate::query::Spawned /// /// # `Iterator::for_each` /// -/// `for_each` methods are seen to be generally faster than directly iterating through `iter` on worlds with high archetype -/// fragmentation, and may enable additional optimizations like [autovectorization]. It is strongly advised to only use -/// [`Iterator::for_each`] if it tangibly improves performance. *Always* be sure profile or benchmark both before and -/// after the change! +/// The `for_each` methods appear to be generally faster than `for`-loops when run on worlds with +/// high archetype fragmentation, and may enable additional optimizations like [autovectorization]. It +/// is strongly advised to only use [`Iterator::for_each`] if it tangibly improves performance. +/// *Always* profile or benchmark before and after the change! /// /// ```rust /// # use bevy_ecs::prelude::*; +/// # /// # #[derive(Component)] /// # struct ComponentA; -/// # fn system( -/// # query: Query<&ComponentA>, -/// # ) { -/// // This might be result in better performance... -/// query.iter().for_each(|component| { -/// // do things with the component -/// }); -/// // ...than this. Always be sure to benchmark to validate the difference! -/// for component in query.iter() { -/// // do things with the component +/// # +/// fn system(query: Query<&ComponentA>) { +/// // This may result in better performance... +/// query.iter().for_each(|component| { +/// // ... +/// }); +/// +/// // ...than this. Always benchmark to validate the difference! +/// for component in query.iter() { +/// // ... +/// } /// } -/// # } -/// # bevy_ecs::system::assert_system_does_not_conflict(system); +/// # +/// # bevy_ecs::system::assert_is_system(system); /// ``` /// -/// [`Component`]: crate::component::Component /// [autovectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization -/// [`Added`]: crate::query::Added -/// [`AnyOf`]: crate::query::AnyOf -/// [binomial coefficient]: https://en.wikipedia.org/wiki/Binomial_coefficient -/// [`Changed`]: crate::query::Changed -/// [components]: crate::component::Component -/// [entity identifiers]: Entity -/// [`EntityRef`]: crate::world::EntityRef -/// [`for_each`]: #iterator-for-each -/// [`get`]: Self::get -/// [`get_many`]: Self::get_many -/// [`get_many_mut`]: Self::get_many_mut -/// [`get_mut`]: Self::get_mut -/// [`single`]: Self::single -/// [`single_mut`]: Self::single_mut -/// [`iter`]: Self::iter -/// [`iter_combinations`]: Self::iter_combinations -/// [`iter_combinations_mut`]: Self::iter_combinations_mut -/// [`iter_many`]: Self::iter_many -/// [`iter_many_mut`]: Self::iter_many_mut -/// [`iter_mut`]: Self::iter_mut -/// [`many`]: Self::many -/// [`many_mut`]: Self::many_mut -/// [`Or`]: crate::query::Or -/// [`par_iter`]: Self::par_iter -/// [`par_iter_mut`]: Self::par_iter_mut -/// [performance]: #performance -/// [`Single`]: Single -/// [`Option`]: Single -/// [`single`]: Self::single -/// [`single_mut`]: Self::single_mut -/// [`SparseSet`]: crate::storage::SparseSet -/// [System parameter]: crate::system::SystemParam -/// [`Table`]: crate::storage::Table -/// [`With`]: crate::query::With -/// [`Without`]: crate::query::Without pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { // SAFETY: Must have access to the components registered in `state`. world: UnsafeWorldCell<'world>, @@ -727,7 +831,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. /// - [`iter_many_inner`](Self::iter_many_inner) to get mutable query items with the full `'world` lifetime. #[inline] - pub fn iter_many>( + pub fn iter_many>( &self, entities: EntityList, ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { @@ -772,7 +876,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`iter_many`](Self::iter_many) to get read-only query items. /// - [`iter_many_inner`](Self::iter_many_inner) to get mutable query items with the full `'world` lifetime. #[inline] - pub fn iter_many_mut>( + pub fn iter_many_mut>( &mut self, entities: EntityList, ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { @@ -790,7 +894,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`iter_many`](Self::iter_many) to get read-only query items. /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many_inner>( + pub fn iter_many_inner>( self, entities: EntityList, ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { @@ -830,7 +934,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// type Item = &'a Entity; /// type IntoIter = UniqueEntityIter>; - /// + /// /// fn into_iter(self) -> Self::IntoIter { /// // SAFETY: `Friends` ensures that it unique_list contains only unique entities. /// unsafe { UniqueEntityIter::from_iterator_unchecked(self.unique_list.iter()) } @@ -1035,7 +1139,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_many_mut`](Self::iter_many_mut) to safely access the query items. - pub unsafe fn iter_many_unsafe>( + pub unsafe fn iter_many_unsafe>( &self, entities: EntityList, ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { @@ -1173,7 +1277,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`par_iter_many_unique_mut`]: Self::par_iter_many_unique_mut /// [`par_iter_mut`]: Self::par_iter_mut #[inline] - pub fn par_iter_many>( + pub fn par_iter_many>( &self, entities: EntityList, ) -> QueryParManyIter<'_, '_, D::ReadOnly, F, EntityList::Item> { @@ -1311,7 +1415,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!( /// match query.get_many([wrong_entity]).unwrap_err() { @@ -1326,7 +1430,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`get_many_mut`](Self::get_many_mut) to get mutable query items. /// - [`get_many_unique`](Self::get_many_unique) to only handle unique inputs. - /// - [`many`](Self::many) for the panicking version. #[inline] pub fn get_many( &self, @@ -1345,7 +1448,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # Examples /// /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, unique_array::UniqueEntityArray, unique_vec::UniqueEntityVec}}; + /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -1363,7 +1466,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!( /// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() { @@ -1386,57 +1489,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.as_readonly().get_many_unique_inner(entities) } - /// Returns the read-only query items for the given array of [`Entity`]. - /// - /// # Panics - /// - /// This method panics if there is a query mismatch or a non-existing entity. - /// - /// # Examples - /// ``` no_run - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Component)] - /// struct Targets([Entity; 3]); - /// - /// #[derive(Component)] - /// struct Position{ - /// x: i8, - /// y: i8 - /// }; - /// - /// impl Position { - /// fn distance(&self, other: &Position) -> i8 { - /// // Manhattan distance is way easier to compute! - /// (self.x - other.x).abs() + (self.y - other.y).abs() - /// } - /// } - /// - /// fn check_all_targets_in_range(targeting_query: Query<(Entity, &Targets, &Position)>, targets_query: Query<&Position>){ - /// for (targeting_entity, targets, origin) in &targeting_query { - /// // We can use "destructuring" to unpack the results nicely - /// let [target_1, target_2, target_3] = targets_query.many(targets.0); - /// - /// assert!(target_1.distance(origin) <= 5); - /// assert!(target_2.distance(origin) <= 5); - /// assert!(target_3.distance(origin) <= 5); - /// } - /// } - /// ``` - /// - /// # See also - /// - /// - [`get_many`](Self::get_many) for the non-panicking version. - #[inline] - #[track_caller] - #[deprecated(note = "Use `get_many` instead and handle the Result.")] - pub fn many(&self, entities: [Entity; N]) -> [ROQueryItem<'_, D>; N] { - match self.get_many(entities) { - Ok(items) => items, - Err(error) => panic!("Cannot get query results: {error}"), - } - } - /// Returns the query item for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. @@ -1559,7 +1611,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// let entities: [Entity; 3] = entities.try_into().unwrap(); /// /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// @@ -1606,7 +1658,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. - /// - [`many_mut`](Self::many_mut) for the panicking version. #[inline] pub fn get_many_mut( &mut self, @@ -1623,18 +1674,18 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # Examples /// /// ``` - /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, unique_array::UniqueEntityArray, unique_vec::UniqueEntityVec}}; + /// use bevy_ecs::{prelude::*, query::QueryEntityError, entity::{EntitySetIterator, UniqueEntityArray, UniqueEntityVec}}; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); /// /// let mut world = World::new(); /// - /// let entity_set: UniqueEntityVec<_> = world.spawn_batch((0..3).map(A)).collect_set(); + /// let entity_set: UniqueEntityVec = world.spawn_batch((0..3).map(A)).collect_set(); /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); /// /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// @@ -1776,64 +1827,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { Ok(values.map(|x| unsafe { x.assume_init() })) } - /// Returns the query items for the given array of [`Entity`]. - /// - /// # Panics - /// - /// This method panics if there is a query mismatch, a non-existing entity, or the same `Entity` is included more than once in the array. - /// - /// # Examples - /// - /// ``` no_run - /// use bevy_ecs::prelude::*; - /// - /// #[derive(Component)] - /// struct Spring{ - /// connected_entities: [Entity; 2], - /// strength: f32, - /// } - /// - /// #[derive(Component)] - /// struct Position { - /// x: f32, - /// y: f32, - /// } - /// - /// #[derive(Component)] - /// struct Force { - /// x: f32, - /// y: f32, - /// } - /// - /// fn spring_forces(spring_query: Query<&Spring>, mut mass_query: Query<(&Position, &mut Force)>){ - /// for spring in &spring_query { - /// // We can use "destructuring" to unpack our query items nicely - /// let [(position_1, mut force_1), (position_2, mut force_2)] = mass_query.many_mut(spring.connected_entities); - /// - /// force_1.x += spring.strength * (position_1.x - position_2.x); - /// force_1.y += spring.strength * (position_1.y - position_2.y); - /// - /// // Silence borrow-checker: I have split your mutable borrow! - /// force_2.x += spring.strength * (position_2.x - position_1.x); - /// force_2.y += spring.strength * (position_2.y - position_1.y); - /// } - /// } - /// ``` - /// - /// # See also - /// - /// - [`get_many_mut`](Self::get_many_mut) for the non panicking version. - /// - [`many`](Self::many) to get read-only query items. - #[inline] - #[track_caller] - #[deprecated(note = "Use `get_many_mut` instead and handle the Result.")] - pub fn many_mut(&mut self, entities: [Entity; N]) -> [D::Item<'_>; N] { - match self.get_many_mut(entities) { - Ok(items) => items, - Err(error) => panic!("Cannot get query result: {error}"), - } - } - /// Returns the query item for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. @@ -1889,12 +1882,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.as_readonly().single_inner() } - /// A deprecated alias for [`single`](Self::single). - #[deprecated(note = "Please use `single` instead")] - pub fn get_single(&self) -> Result, QuerySingleError> { - self.single() - } - /// Returns a single query item when there is exactly one entity matching the query. /// /// If the number of query items is not exactly one, a [`QuerySingleError`] is returned instead. @@ -1924,12 +1911,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.reborrow().single_inner() } - /// A deprecated alias for [`single_mut`](Self::single_mut). - #[deprecated(note = "Please use `single_mut` instead")] - pub fn get_single_mut(&mut self) -> Result, QuerySingleError> { - self.single_mut() - } - /// Returns a single query item when there is exactly one entity matching the query. /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. /// @@ -1976,8 +1957,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)` /// where `n` is the number of *potential* matches. This can be notably expensive for queries that rely - /// on non-archetypal filters such as [`Added`] or [`Changed`] which must individually check each query - /// result for a match. + /// on non-archetypal filters such as [`Added`], [`Changed`] or [`Spawned`] which must individually check + /// each query result for a match. /// /// # Example /// @@ -2000,6 +1981,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// [`Added`]: crate::query::Added /// [`Changed`]: crate::query::Changed + /// [`Spawned`]: crate::query::Spawned #[inline] pub fn is_empty(&self) -> bool { self.as_nop().iter().next().is_none() @@ -2038,9 +2020,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`. /// This can be useful for passing the query to another function. Note that since - /// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added) and - /// [`Changed`](crate::query::Changed) will not be respected. To maintain or change filter - /// terms see [`Self::transmute_lens_filtered`] + /// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added), + /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be + /// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`] /// /// ## Panics /// @@ -2095,12 +2077,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// * Tuples of query data and `#[derive(QueryData)]` structs have the union of the access of their subqueries /// * [`EntityMut`](crate::world::EntityMut) has read and write access to all components, but no required access /// * [`EntityRef`](crate::world::EntityRef) has read access to all components, but no required access - /// * [`Entity`], [`EntityLocation`], [`&Archetype`], [`Has`], and [`PhantomData`] have no access at all, + /// * [`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has`], and [`PhantomData`] have no access at all, /// so can be added to any query /// * [`FilteredEntityRef`](crate::world::FilteredEntityRef) and [`FilteredEntityMut`](crate::world::FilteredEntityMut) /// have access determined by the [`QueryBuilder`](crate::query::QueryBuilder) used to construct them. - /// Any query can be transmuted to them, and they will receive the access of the source query, - /// but only if they are the top-level query and not nested + /// Any query can be transmuted to them, and they will receive the access of the source query. + /// When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data. /// * [`Added`](crate::query::Added) and [`Changed`](crate::query::Changed) filters have read and required access to `T` /// * [`With`](crate::query::With) and [`Without`](crate::query::Without) filters have no access at all, /// so can be added to any query @@ -2172,9 +2154,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// // Anything can be transmuted to `FilteredEntityRef` or `FilteredEntityMut` /// // This will create a `FilteredEntityMut` that only has read access to `T` /// assert_valid_transmute::<&T, FilteredEntityMut>(); - /// // This transmute will succeed, but the `FilteredEntityMut` will have no access! - /// // It must be the top-level query to be given access, but here it is nested in a tuple. - /// assert_valid_transmute::<&T, (Entity, FilteredEntityMut)>(); + /// // This will create a `FilteredEntityMut` that has no access to `T`, + /// // read access to `U`, and write access to `V`. + /// assert_valid_transmute::<(&mut T, &mut U, &mut V), (&mut T, &U, FilteredEntityMut)>(); /// /// // `Added` and `Changed` filters have the same access as `&T` data /// // Remember that they are only evaluated on the transmuted query, not the original query! @@ -2185,6 +2167,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// ``` /// /// [`EntityLocation`]: crate::entity::EntityLocation + /// [`SpawnDetails`]: crate::query::SpawnDetails /// [`&Archetype`]: crate::archetype::Archetype /// [`Has`]: crate::query::Has #[track_caller] @@ -2197,9 +2180,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`. /// This can be useful for passing the query to another function. Note that since - /// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added) and - /// [`Changed`](crate::query::Changed) will not be respected. To maintain or change filter - /// terms see [`Self::transmute_lens_filtered`] + /// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added), + /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be + /// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`] /// /// ## Panics /// @@ -2254,7 +2237,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// * `&mut T` -> `&T` /// * `&mut T` -> `Ref` /// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef) - /// + /// /// [`EntityLocation`]: crate::entity::EntityLocation /// [`&Archetype`]: crate::archetype::Archetype /// @@ -2270,8 +2253,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// Note that the lens will iterate the same tables and archetypes as the original query. This means that /// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without) - /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added) and - /// [`Changed`](crate::query::Changed) will only be respected if they are in the type signature. + /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added), + /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they + /// are in the type signature. #[track_caller] pub fn transmute_lens_filtered( &mut self, @@ -2284,8 +2268,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// Note that the lens will iterate the same tables and archetypes as the original query. This means that /// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without) - /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added) and - /// [`Changed`](crate::query::Changed) will only be respected if they are in the type signature. + /// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added), + /// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they /// /// # See also /// @@ -2321,7 +2305,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`. /// The returned query will only return items with both `A` and `B`. Note that since filters - /// are dropped, non-archetypal filters like `Added` and `Changed` will not be respected. + /// are dropped, non-archetypal filters like `Added`, `Changed` and `Spawned` will not be respected. /// To maintain or change filter terms see `Self::join_filtered`. /// /// ## Example @@ -2383,7 +2367,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`. /// The returned query will only return items with both `A` and `B`. Note that since filters - /// are dropped, non-archetypal filters like `Added` and `Changed` will not be respected. + /// are dropped, non-archetypal filters like `Added`, `Changed` and `Spawned` will not be respected. /// To maintain or change filter terms see `Self::join_filtered`. /// /// ## Panics @@ -2410,8 +2394,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Note that the lens with iterate a subset of the original queries' tables /// and archetypes. This means that additional archetypal query terms like /// `With` and `Without` will not necessarily be respected and non-archetypal - /// terms like `Added` and `Changed` will only be respected if they are in - /// the type signature. + /// terms like `Added`, `Changed` and `Spawned` will only be respected if they + /// are in the type signature. pub fn join_filtered< 'a, OtherD: QueryData, @@ -2431,8 +2415,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Note that the lens with iterate a subset of the original queries' tables /// and archetypes. This means that additional archetypal query terms like /// `With` and `Without` will not necessarily be respected and non-archetypal - /// terms like `Added` and `Changed` will only be respected if they are in - /// the type signature. + /// terms like `Added`, `Changed` and `Spawned` will only be respected if they + /// are in the type signature. /// /// # See also /// @@ -2574,7 +2558,7 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`]. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. -/// This will cause a panic, but can be configured to do nothing or warn once. +/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). /// /// Use [`Option>`] instead if zero or one matching entities can exist. /// @@ -2610,11 +2594,12 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { /// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist. -/// This will cause a panic, but can be configured to do nothing or warn once. +/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). /// /// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches. -/// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added) or [`Changed`](crate::query::Changed) -/// which must individually check each query result for a match. +/// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added), +/// [`Changed`](crate::query::Changed) of [`Spawned`](crate::query::Spawned) which must individually check each query +/// result for a match. /// /// See [`Query`] for more details. /// diff --git a/crates/bevy_ecs/src/system/schedule_system.rs b/crates/bevy_ecs/src/system/schedule_system.rs index 749060d2b9..962ff94a2a 100644 --- a/crates/bevy_ecs/src/system/schedule_system.rs +++ b/crates/bevy_ecs/src/system/schedule_system.rs @@ -1,27 +1,26 @@ use alloc::{borrow::Cow, vec::Vec}; use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, error::Result, - query::Access, - system::{input::SystemIn, BoxedSystem, System}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, + query::{Access, FilteredAccessSet}, + system::{input::SystemIn, BoxedSystem, System, SystemInput}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, }; -use super::IntoSystem; +use super::{IntoSystem, SystemParamValidationError}; /// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`] -pub struct InfallibleSystemWrapper>(S); +pub struct InfallibleSystemWrapper>(S); -impl> InfallibleSystemWrapper { +impl> InfallibleSystemWrapper { /// Create a new `OkWrapperSystem` pub fn new(system: S) -> Self { Self(IntoSystem::into_system(system)) } } -impl> System for InfallibleSystemWrapper { +impl> System for InfallibleSystemWrapper { type In = (); type Out = Result; @@ -30,14 +29,18 @@ impl> System for InfallibleSystemWrapper { self.0.name() } + fn type_id(&self) -> core::any::TypeId { + self.0.type_id() + } + #[inline] fn component_access(&self) -> &Access { self.0.component_access() } #[inline] - fn archetype_component_access(&self) -> &Access { - self.0.archetype_component_access() + fn component_access_set(&self) -> &FilteredAccessSet { + self.0.component_access_set() } #[inline] @@ -65,12 +68,6 @@ impl> System for InfallibleSystemWrapper { Ok(()) } - #[inline] - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { - self.0.run(input, world); - Ok(()) - } - #[inline] fn apply_deferred(&mut self, world: &mut World) { self.0.apply_deferred(world); @@ -82,7 +79,10 @@ impl> System for InfallibleSystemWrapper { } #[inline] - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { self.0.validate_param_unsafe(world) } @@ -91,11 +91,6 @@ impl> System for InfallibleSystemWrapper { self.0.initialize(world); } - #[inline] - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - self.0.update_archetype_component_access(world); - } - #[inline] fn check_change_tick(&mut self, change_tick: Tick) { self.0.check_change_tick(change_tick); @@ -116,5 +111,222 @@ impl> System for InfallibleSystemWrapper { } } +/// See [`IntoSystem::with_input`] for details. +pub struct WithInputWrapper +where + for<'i> S: System = &'i mut T>>, + T: Send + Sync + 'static, +{ + system: S, + value: T, +} + +impl WithInputWrapper +where + for<'i> S: System = &'i mut T>>, + T: Send + Sync + 'static, +{ + /// Wraps the given system with the given input value. + pub fn new(system: impl IntoSystem, value: T) -> Self { + Self { + system: IntoSystem::into_system(system), + value, + } + } + + /// Returns a reference to the input value. + pub fn value(&self) -> &T { + &self.value + } + + /// Returns a mutable reference to the input value. + pub fn value_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl System for WithInputWrapper +where + for<'i> S: System = &'i mut T>>, + T: Send + Sync + 'static, +{ + type In = (); + + type Out = S::Out; + + fn name(&self) -> Cow<'static, str> { + self.system.name() + } + + fn component_access(&self) -> &Access { + self.system.component_access() + } + + fn component_access_set(&self) -> &FilteredAccessSet { + self.system.component_access_set() + } + + fn is_send(&self) -> bool { + self.system.is_send() + } + + fn is_exclusive(&self) -> bool { + self.system.is_exclusive() + } + + fn has_deferred(&self) -> bool { + self.system.has_deferred() + } + + unsafe fn run_unsafe( + &mut self, + _input: SystemIn<'_, Self>, + world: UnsafeWorldCell, + ) -> Self::Out { + self.system.run_unsafe(&mut self.value, world) + } + + fn apply_deferred(&mut self, world: &mut World) { + self.system.apply_deferred(world); + } + + fn queue_deferred(&mut self, world: DeferredWorld) { + self.system.queue_deferred(world); + } + + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + self.system.validate_param_unsafe(world) + } + + fn initialize(&mut self, world: &mut World) { + self.system.initialize(world); + } + + fn check_change_tick(&mut self, change_tick: Tick) { + self.system.check_change_tick(change_tick); + } + + fn get_last_run(&self) -> Tick { + self.system.get_last_run() + } + + fn set_last_run(&mut self, last_run: Tick) { + self.system.set_last_run(last_run); + } +} + +/// Constructed in [`IntoSystem::with_input_from`]. +pub struct WithInputFromWrapper { + system: S, + value: Option, +} + +impl WithInputFromWrapper +where + for<'i> S: System = &'i mut T>>, + T: Send + Sync + 'static, +{ + /// Wraps the given system. + pub fn new(system: impl IntoSystem) -> Self { + Self { + system: IntoSystem::into_system(system), + value: None, + } + } + + /// Returns a reference to the input value, if it has been initialized. + pub fn value(&self) -> Option<&T> { + self.value.as_ref() + } + + /// Returns a mutable reference to the input value, if it has been initialized. + pub fn value_mut(&mut self) -> Option<&mut T> { + self.value.as_mut() + } +} + +impl System for WithInputFromWrapper +where + for<'i> S: System = &'i mut T>>, + T: FromWorld + Send + Sync + 'static, +{ + type In = (); + + type Out = S::Out; + + fn name(&self) -> Cow<'static, str> { + self.system.name() + } + + fn component_access(&self) -> &Access { + self.system.component_access() + } + + fn component_access_set(&self) -> &FilteredAccessSet { + self.system.component_access_set() + } + + fn is_send(&self) -> bool { + self.system.is_send() + } + + fn is_exclusive(&self) -> bool { + self.system.is_exclusive() + } + + fn has_deferred(&self) -> bool { + self.system.has_deferred() + } + + unsafe fn run_unsafe( + &mut self, + _input: SystemIn<'_, Self>, + world: UnsafeWorldCell, + ) -> Self::Out { + let value = self + .value + .as_mut() + .expect("System input value was not found. Did you forget to initialize the system before running it?"); + self.system.run_unsafe(value, world) + } + + fn apply_deferred(&mut self, world: &mut World) { + self.system.apply_deferred(world); + } + + fn queue_deferred(&mut self, world: DeferredWorld) { + self.system.queue_deferred(world); + } + + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + self.system.validate_param_unsafe(world) + } + + fn initialize(&mut self, world: &mut World) { + self.system.initialize(world); + if self.value.is_none() { + self.value = Some(T::from_world(world)); + } + } + + fn check_change_tick(&mut self, change_tick: Tick) { + self.system.check_change_tick(change_tick); + } + + fn get_last_run(&self) -> Tick { + self.system.get_last_run() + } + + fn set_last_run(&mut self, last_run: Tick) { + self.system.set_last_run(last_run); + } +} + /// Type alias for a `BoxedSystem` that a `Schedule` can store. pub type ScheduleSystem = BoxedSystem<(), Result>; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index b44cd29440..be650588bd 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -7,9 +7,8 @@ use log::warn; use thiserror::Error; use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, - query::Access, + query::{Access, FilteredAccessSet}, schedule::InternedSystemSet, system::{input::SystemInput, SystemIn}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, @@ -18,7 +17,7 @@ use crate::{ use alloc::{borrow::Cow, boxed::Box, vec::Vec}; use core::any::TypeId; -use super::IntoSystem; +use super::{IntoSystem, SystemParamValidationError}; /// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule) /// @@ -44,10 +43,13 @@ pub trait System: Send + Sync + 'static { fn type_id(&self) -> TypeId { TypeId::of::() } + /// Returns the system's component [`Access`]. fn component_access(&self) -> &Access; - /// Returns the system's archetype component [`Access`]. - fn archetype_component_access(&self) -> &Access; + + /// Returns the system's component [`FilteredAccessSet`]. + fn component_access_set(&self) -> &FilteredAccessSet; + /// Returns true if the system is [`Send`]. fn is_send(&self) -> bool; @@ -67,11 +69,10 @@ pub trait System: Send + Sync + 'static { /// # Safety /// /// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data - /// registered in `archetype_component_access`. There must be no conflicting + /// registered in `component_access_set`. There must be no conflicting /// simultaneous accesses while the system is running. - /// - The method [`System::update_archetype_component_access`] must be called at some - /// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`] - /// panics (or otherwise does not return for any reason), this method must not be called. + /// - If [`System::is_exclusive`] returns `true`, then it must be valid to call + /// [`UnsafeWorldCell::world_mut`] on `world`. unsafe fn run_unsafe(&mut self, input: SystemIn<'_, Self>, world: UnsafeWorldCell) -> Self::Out; @@ -97,10 +98,8 @@ pub trait System: Send + Sync + 'static { world: &mut World, ) -> Self::Out { let world_cell = world.as_unsafe_world_cell(); - self.update_archetype_component_access(world_cell); // SAFETY: // - We have exclusive access to the entire world. - // - `update_archetype_component_access` has been called. unsafe { self.run_unsafe(input, world_cell) } } @@ -116,7 +115,7 @@ pub trait System: Send + Sync + 'static { /// Validates that all parameters can be acquired and that system can run without panic. /// Built-in executors use this to prevent invalid systems from running. /// - /// However calling and respecting [`System::validate_param_unsafe`] or it's safe variant + /// However calling and respecting [`System::validate_param_unsafe`] or its safe variant /// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`] /// should provide their own safety mechanism to prevent undefined behavior. /// @@ -127,34 +126,25 @@ pub trait System: Send + Sync + 'static { /// # Safety /// /// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data - /// registered in `archetype_component_access`. There must be no conflicting + /// registered in `component_access_set`. There must be no conflicting /// simultaneous accesses while the system is running. - /// - The method [`System::update_archetype_component_access`] must be called at some - /// point before this one, with the same exact [`World`]. If [`System::update_archetype_component_access`] - /// panics (or otherwise does not return for any reason), this method must not be called. - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool; + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError>; /// Safe version of [`System::validate_param_unsafe`]. /// that runs on exclusive, single-threaded `world` pointer. - fn validate_param(&mut self, world: &World) -> bool { + fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> { let world_cell = world.as_unsafe_world_cell_readonly(); - self.update_archetype_component_access(world_cell); // SAFETY: // - We have exclusive access to the entire world. - // - `update_archetype_component_access` has been called. unsafe { self.validate_param_unsafe(world_cell) } } /// Initialize the system. fn initialize(&mut self, _world: &mut World); - /// Update the system's archetype component [`Access`]. - /// - /// ## Note for implementors - /// `world` may only be used to access metadata. This can be done in safe code - /// via functions such as [`UnsafeWorldCell::archetypes`]. - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell); - /// Checks any [`Tick`]s stored on this system and wraps their value if they get too old. /// /// This method must be called periodically to ensure that change detection behaves correctly. @@ -200,10 +190,8 @@ pub unsafe trait ReadOnlySystem: System { /// since this system is known not to modify the world. fn run_readonly(&mut self, input: SystemIn<'_, Self>, world: &World) -> Self::Out { let world = world.as_unsafe_world_cell_readonly(); - self.update_archetype_component_access(world); // SAFETY: // - We have read-only access to the entire world. - // - `update_archetype_component_access` has been called. unsafe { self.run_unsafe(input, world) } } } @@ -363,36 +351,35 @@ impl RunSystemOnce for &mut World { { let mut system: T::System = IntoSystem::into_system(system); system.initialize(self); - if system.validate_param(self) { - Ok(system.run(input, self)) - } else { - Err(RunSystemError::InvalidParams(system.name())) - } + system + .validate_param(self) + .map_err(|err| RunSystemError::InvalidParams { + system: system.name(), + err, + })?; + Ok(system.run(input, self)) } } /// Running system failed. -#[derive(Error)] +#[derive(Error, Debug)] pub enum RunSystemError { /// System could not be run due to parameters that failed validation. - /// - /// This can occur because the data required by the system was not present in the world. - #[error("The data required by the system {0:?} was not found in the world and the system did not run due to failed parameter validation.")] - InvalidParams(Cow<'static, str>), -} - -impl Debug for RunSystemError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(), - } - } + /// This should not be considered an error if [`field@SystemParamValidationError::skipped`] is `true`. + #[error("System {system} did not run due to failed parameter validation: {err}")] + InvalidParams { + /// The identifier of the system that was run. + system: Cow<'static, str>, + /// The returned parameter validation error. + err: SystemParamValidationError, + }, } #[cfg(test)] mod tests { use super::*; use crate::prelude::*; + use alloc::string::ToString; #[test] fn run_system_once() { @@ -462,8 +449,10 @@ mod tests { let mut world = World::default(); // This fails because `T` has not been added to the world yet. - let result = world.run_system_once(system.warn_param_missing()); + let result = world.run_system_once(system); - assert!(matches!(result, Err(RunSystemError::InvalidParams(_)))); + assert!(matches!(result, Err(RunSystemError::InvalidParams { .. }))); + let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res` failed validation: Resource does not exist"; + assert_eq!(expected, result.unwrap_err().to_string()); } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index ddfcddaa76..df14530f48 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1,6 +1,6 @@ pub use crate::change_detection::{NonSendMut, Res, ResMut}; use crate::{ - archetype::{Archetype, Archetypes}, + archetype::Archetypes, bundle::Bundles, change_detection::{MaybeLocation, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick}, @@ -17,18 +17,23 @@ use crate::{ FromWorld, World, }, }; -use alloc::{borrow::ToOwned, boxed::Box, vec::Vec}; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + vec::Vec, +}; pub use bevy_ecs_macros::SystemParam; +use bevy_platform::cell::SyncCell; use bevy_ptr::UnsafeCellDeref; -use bevy_utils::synccell::SyncCell; use core::{ any::Any, - fmt::Debug, + fmt::{Debug, Display}, marker::PhantomData, ops::{Deref, DerefMut}, panic::Location, }; use disqualified::ShortName; +use thiserror::Error; use super::Populated; use variadics_please::{all_tuples, all_tuples_enumerated}; @@ -126,6 +131,29 @@ use variadics_please::{all_tuples, all_tuples_enumerated}; /// This will most commonly occur when working with `SystemParam`s generically, as the requirement /// has not been proven to the compiler. /// +/// ## Custom Validation Messages +/// +/// When using the derive macro, any [`SystemParamValidationError`]s will be propagated from the sub-parameters. +/// If you want to override the error message, add a `#[system_param(validation_message = "New message")]` attribute to the parameter. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Resource)] +/// # struct SomeResource; +/// # use bevy_ecs::system::SystemParam; +/// # +/// #[derive(SystemParam)] +/// struct MyParam<'w> { +/// #[system_param(validation_message = "Custom Message")] +/// foo: Res<'w, SomeResource>, +/// } +/// +/// let mut world = World::new(); +/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err(); +/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message"; +/// assert!(err.to_string().ends_with(expected)); +/// ``` +/// /// ## Builders /// /// If you want to use a [`SystemParamBuilder`](crate::system::SystemParamBuilder) with a derived [`SystemParam`] implementation, @@ -196,22 +224,6 @@ pub unsafe trait SystemParam: Sized { /// and creates a new instance of this param's [`State`](SystemParam::State). fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; - /// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable).a - /// - /// # Safety - /// `archetype` must be from the [`World`] used to initialize `state` in [`SystemParam::init_state`]. - #[inline] - #[expect( - unused_variables, - reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." - )] - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - } - /// Applies any deferred mutations stored in this [`SystemParam`]'s state. /// This is used to apply [`Commands`] during [`ApplyDeferred`](crate::prelude::ApplyDeferred). /// @@ -232,7 +244,11 @@ pub unsafe trait SystemParam: Sized { fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {} /// Validates that the param can be acquired by the [`get_param`](SystemParam::get_param). - /// Built-in executors use this to prevent systems with invalid params from running. + /// + /// Built-in executors use this to prevent systems with invalid params from running, + /// and any failures here will be bubbled up to the default error handler defined in [`bevy_ecs::error`], + /// with a value of type [`SystemParamValidationError`]. + /// /// For nested [`SystemParam`]s validation will fail if any /// delegated validation fails. /// @@ -252,24 +268,24 @@ pub unsafe trait SystemParam: Sized { /// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior, /// the validity of the param may change. /// + /// [`System::validate_param`](super::system::System::validate_param), + /// calls this method for each supplied system param. + /// /// # Safety /// /// - The passed [`UnsafeWorldCell`] must have read-only access to world data /// registered in [`init_state`](SystemParam::init_state). /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - /// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype). #[expect( unused_variables, reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion." )] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { - // By default we allow panics in [`SystemParam::get_param`] and return `true`. - // Preventing panics is an optional feature. - true + ) -> Result<(), SystemParamValidationError> { + Ok(()) } /// Creates a parameter to be passed into a [`SystemParamFunction`](super::SystemParamFunction). @@ -279,7 +295,6 @@ pub unsafe trait SystemParam: Sized { /// - The passed [`UnsafeWorldCell`] must have access to any world data registered /// in [`init_state`](SystemParam::init_state). /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - /// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype). unsafe fn get_param<'world, 'state>( state: &'state mut Self::State, system_meta: &SystemMeta, @@ -303,26 +318,18 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re { } -// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If // this Query conflicts with any prior access, a panic will occur. unsafe impl SystemParam for Query<'_, '_, D, F> { type State = QueryState; type Item<'w, 's> = Query<'w, 's, D, F>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - let state = QueryState::new_with_access(world, &mut system_meta.archetype_component_access); + let state = QueryState::new(world); init_query_param(world, system_meta, &state); state } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - state.new_archetype(archetype, &mut system_meta.archetype_component_access); - } - #[inline] unsafe fn get_param<'w, 's>( state: &'s mut Self::State, @@ -334,7 +341,7 @@ unsafe impl SystemParam for Qu // so the caller ensures that `world` has permission to access any // world data that the query needs. // The caller ensures the world matches the one used in init_state. - unsafe { state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) } + unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) } } } @@ -376,7 +383,7 @@ fn assert_component_access_compatibility( panic!("error[B0001]: Query<{}, {}> in system {system_name} accesses component(s) {accesses}in a way that conflicts with a previous system parameter. Consider using `Without` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001", ShortName(query_type), ShortName(filter_type)); } -// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If // this Query conflicts with any prior access, a panic will occur. unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Single<'a, D, F> { type State = QueryState; @@ -386,15 +393,6 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo Query::init_state(world, system_meta) } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: Delegate to existing `SystemParam` implementations. - unsafe { Query::new_archetype(state, archetype, system_meta) }; - } - #[inline] unsafe fn get_param<'w, 's>( state: &'s mut Self::State, @@ -404,9 +402,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo ) -> Self::Item<'w, 's> { // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. // The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) - }; + let query = + unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) }; let single = query .single_inner() .expect("The query was expected to contain exactly one matching entity."); @@ -418,95 +415,26 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere // and the query is read only. // The caller ensures the world matches the one used in init_state. let query = unsafe { - state.query_unchecked_manual_with_ticks( - world, - system_meta.last_run, - world.change_tick(), - ) - }; - let is_valid = query.single_inner().is_ok(); - if !is_valid { - system_meta.try_warn_param::(); - } - is_valid - } -} - -// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If -// this Query conflicts with any prior access, a panic will occur. -unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam - for Option> -{ - type State = QueryState; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - Single::init_state(world, system_meta) - } - - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: Delegate to existing `SystemParam` implementations. - unsafe { Single::new_archetype(state, archetype, system_meta) }; - } - - #[inline] - unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - state.validate_world(world.id()); - // SAFETY: State ensures that the components it accesses are not accessible elsewhere. - // The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) }; match query.single_inner() { - Ok(single) => Some(Single { - item: single, - _filter: PhantomData, - }), - Err(QuerySingleError::NoEntities(_)) => None, - Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e), + Ok(_) => Ok(()), + Err(QuerySingleError::NoEntities(_)) => Err( + SystemParamValidationError::skipped::("No matching entities"), + ), + Err(QuerySingleError::MultipleEntities(_)) => Err( + SystemParamValidationError::skipped::("Multiple matching entities"), + ), } } - - #[inline] - unsafe fn validate_param( - state: &Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell, - ) -> bool { - // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere - // and the query is read only. - // The caller ensures the world matches the one used in init_state. - let query = unsafe { - state.query_unchecked_manual_with_ticks( - world, - system_meta.last_run, - world.change_tick(), - ) - }; - let result = query.single_inner(); - let is_valid = !matches!(result, Err(QuerySingleError::MultipleEntities(_))); - if !is_valid { - system_meta.try_warn_param::(); - } - is_valid - } } // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. @@ -515,13 +443,7 @@ unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOn { } -// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam - for Option> -{ -} - -// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If // this Query conflicts with any prior access, a panic will occur. unsafe impl SystemParam for Populated<'_, '_, D, F> @@ -533,15 +455,6 @@ unsafe impl SystemParam Query::init_state(world, system_meta) } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: Delegate to existing `SystemParam` implementations. - unsafe { Query::new_archetype(state, archetype, system_meta) }; - } - #[inline] unsafe fn get_param<'w, 's>( state: &'s mut Self::State, @@ -556,21 +469,23 @@ unsafe impl SystemParam #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: // - We have read-only access to the components accessed by query. // - The caller ensures the world matches the one used in init_state. let query = unsafe { - state.query_unchecked_manual_with_ticks( - world, - system_meta.last_run, - world.change_tick(), - ) + state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) }; - !query.is_empty() + if query.is_empty() { + Err(SystemParamValidationError::skipped::( + "No matching entities", + )) + } else { + Ok(()) + } } } @@ -707,7 +622,7 @@ macro_rules! impl_param_set { where $($param: ReadOnlySystemParam,)* { } - // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts + // SAFETY: Relevant parameter ComponentId access is applied to SystemMeta. If any ParamState conflicts // with any prior access, a panic will occur. unsafe impl<'_w, '_s, $($param: SystemParam,)*> SystemParam for ParamSet<'_w, '_s, ($($param,)*)> { @@ -727,7 +642,6 @@ macro_rules! impl_param_set { // Pretend to add each param to the system alone, see if it conflicts let mut $system_meta = system_meta.clone(); $system_meta.component_access_set.clear(); - $system_meta.archetype_component_access.clear(); $param::init_state(world, &mut $system_meta); // The variable is being defined with non_snake_case here let $param = $param::init_state(world, &mut system_meta.clone()); @@ -740,18 +654,10 @@ macro_rules! impl_param_set { system_meta .component_access_set .extend($system_meta.component_access_set); - system_meta - .archetype_component_access - .extend(&$system_meta.archetype_component_access); )* ($($param,)*) } - unsafe fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { <($($param,)*) as SystemParam>::new_archetype(state, archetype, system_meta); } - } - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { <($($param,)*) as SystemParam>::apply(state, system_meta, world); } @@ -762,10 +668,10 @@ macro_rules! impl_param_set { #[inline] unsafe fn validate_param<'w, 's>( - state: &'s Self::State, + state: &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { <($($param,)*) as SystemParam>::validate_param(state, system_meta, world) } @@ -810,7 +716,7 @@ all_tuples_enumerated!(impl_param_set, 1, 8, P, m, p); // SAFETY: Res only reads a single World resource unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} -// SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res +// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { type State = ComponentId; @@ -818,7 +724,6 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let component_id = world.components_registrator().register_resource::(); - let archetype_component_id = world.initialize_resource_internal(component_id).id(); let combined_access = system_meta.component_access_set.combined_access(); assert!( @@ -831,28 +736,27 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { .component_access_set .add_unfiltered_resource_read(component_id); - system_meta - .archetype_component_access - .add_resource_read(archetype_component_id); - component_id } #[inline] unsafe fn validate_param( - &component_id: &Self::State, - system_meta: &SystemMeta, + &mut component_id: &mut Self::State, + _system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - let is_valid = unsafe { world.storages() } + if unsafe { world.storages() } .resources .get(component_id) - .is_some_and(ResourceData::is_present); - if !is_valid { - system_meta.try_warn_param::(); + .is_some_and(ResourceData::is_present) + { + Ok(()) + } else { + Err(SystemParamValidationError::invalid::( + "Resource does not exist", + )) } - is_valid } #[inline] @@ -885,41 +789,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { } } -// SAFETY: Only reads a single World resource -unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `Res`, which initializes and validates the correct world access. -unsafe impl<'a, T: Resource> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - Res::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_resource_with_ticks(component_id) - .map(|(ptr, ticks, caller)| Res { - value: ptr.deref(), - ticks: Ticks { - added: ticks.added.deref(), - changed: ticks.changed.deref(), - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: caller.map(|caller| caller.deref()), - }) - } -} - -// SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res +// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { type State = ComponentId; @@ -927,7 +797,6 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { let component_id = world.components_registrator().register_resource::(); - let archetype_component_id = world.initialize_resource_internal(component_id).id(); let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_resource_write(component_id) { @@ -943,28 +812,27 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { .component_access_set .add_unfiltered_resource_write(component_id); - system_meta - .archetype_component_access - .add_resource_write(archetype_component_id); - component_id } #[inline] unsafe fn validate_param( - &component_id: &Self::State, - system_meta: &SystemMeta, + &mut component_id: &mut Self::State, + _system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - let is_valid = unsafe { world.storages() } + if unsafe { world.storages() } .resources .get(component_id) - .is_some_and(ResourceData::is_present); - if !is_valid { - system_meta.try_warn_param::(); + .is_some_and(ResourceData::is_present) + { + Ok(()) + } else { + Err(SystemParamValidationError::invalid::( + "Resource does not exist", + )) } - is_valid } #[inline] @@ -996,37 +864,6 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { } } -// SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access. -unsafe impl<'a, T: Resource> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - ResMut::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_resource_mut_by_id(component_id) - .map(|value| ResMut { - value: value.value.deref_mut::(), - ticks: TicksMut { - added: value.ticks.added, - changed: value.ticks.changed, - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: value.changed_by, - }) - } -} - /// SAFETY: only reads world unsafe impl<'w> ReadOnlySystemParam for &'w World {} @@ -1036,16 +873,6 @@ unsafe impl SystemParam for &'_ World { type Item<'w, 's> = &'w World; fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - let mut access = Access::default(); - access.read_all(); - if !system_meta - .archetype_component_access - .is_compatible(&access) - { - panic!("&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"); - } - system_meta.archetype_component_access.extend(&access); - let mut filtered_access = FilteredAccess::default(); filtered_access.read_all(); @@ -1086,7 +913,6 @@ unsafe impl<'w> SystemParam for DeferredWorld<'w> { system_meta.name, ); system_meta.component_access_set.write_all(); - system_meta.archetype_component_access.write_all(); } unsafe fn get_param<'world, 'state>( @@ -1420,6 +1246,33 @@ unsafe impl SystemParam for Deferred<'_, T> { } } +/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread. +pub struct NonSendMarker(PhantomData<*mut ()>); + +// SAFETY: No world access. +unsafe impl SystemParam for NonSendMarker { + type State = (); + type Item<'w, 's> = Self; + + #[inline] + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + system_meta.set_non_send(); + } + + #[inline] + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &SystemMeta, + _world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + Self(PhantomData) + } +} + +// SAFETY: Does not read any world state +unsafe impl ReadOnlySystemParam for NonSendMarker {} + /// Shared borrow of a non-[`Send`] resource. /// /// Only `Send` resources may be accessed with the [`Res`] [`SystemParam`]. In case that the @@ -1490,7 +1343,7 @@ impl<'a, T> From> for NonSend<'a, T> { } } -// SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this +// SAFETY: NonSendComponentId access is applied to SystemMeta. If this // NonSend conflicts with any prior access, a panic will occur. unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { type State = ComponentId; @@ -1500,7 +1353,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { system_meta.set_non_send(); let component_id = world.components_registrator().register_non_send::(); - let archetype_component_id = world.initialize_non_send_internal(component_id).id(); let combined_access = system_meta.component_access_set.combined_access(); assert!( @@ -1513,28 +1365,27 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { .component_access_set .add_unfiltered_resource_read(component_id); - system_meta - .archetype_component_access - .add_resource_read(archetype_component_id); - component_id } #[inline] unsafe fn validate_param( - &component_id: &Self::State, - system_meta: &SystemMeta, + &mut component_id: &mut Self::State, + _system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - let is_valid = unsafe { world.storages() } + if unsafe { world.storages() } .non_send_resources .get(component_id) - .is_some_and(ResourceData::is_present); - if !is_valid { - system_meta.try_warn_param::(); + .is_some_and(ResourceData::is_present) + { + Ok(()) + } else { + Err(SystemParamValidationError::invalid::( + "Non-send resource does not exist", + )) } - is_valid } #[inline] @@ -1565,38 +1416,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { } } -// SAFETY: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParam for Option> {} - -// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access. -unsafe impl SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSend::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks, caller)| NonSend { - value: ptr.deref(), - ticks: ticks.read(), - last_run: system_meta.last_run, - this_run: change_tick, - changed_by: caller.map(|caller| caller.deref()), - }) - } -} - -// SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this +// SAFETY: NonSendMut ComponentId access is applied to SystemMeta. If this // NonSendMut conflicts with any prior access, a panic will occur. unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { type State = ComponentId; @@ -1606,7 +1426,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { system_meta.set_non_send(); let component_id = world.components_registrator().register_non_send::(); - let archetype_component_id = world.initialize_non_send_internal(component_id).id(); let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_component_write(component_id) { @@ -1622,28 +1441,27 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { .component_access_set .add_unfiltered_resource_write(component_id); - system_meta - .archetype_component_access - .add_resource_write(archetype_component_id); - component_id } #[inline] unsafe fn validate_param( - &component_id: &Self::State, - system_meta: &SystemMeta, + &mut component_id: &mut Self::State, + _system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - let is_valid = unsafe { world.storages() } + if unsafe { world.storages() } .non_send_resources .get(component_id) - .is_some_and(ResourceData::is_present); - if !is_valid { - system_meta.try_warn_param::(); + .is_some_and(ResourceData::is_present) + { + Ok(()) + } else { + Err(SystemParamValidationError::invalid::( + "Non-send resource does not exist", + )) } - is_valid } #[inline] @@ -1671,32 +1489,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { } } -// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access. -unsafe impl<'a, T: 'static> SystemParam for Option> { - type State = ComponentId; - type Item<'w, 's> = Option>; - - fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - NonSendMut::::init_state(world, system_meta) - } - - #[inline] - unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, - system_meta: &SystemMeta, - world: UnsafeWorldCell<'w>, - change_tick: Tick, - ) -> Self::Item<'w, 's> { - world - .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks, caller)| NonSendMut { - value: ptr.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - changed_by: caller.map(|caller| caller.deref_mut()), - }) - } -} - // SAFETY: Only reads World archetypes unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {} @@ -1790,7 +1582,7 @@ unsafe impl<'a> SystemParam for &'a Bundles { /// Component change ticks that are more recent than `last_run` will be detected by the system. /// Those can be read by calling [`last_changed`](crate::change_detection::DetectChanges::last_changed) /// on a [`Mut`](crate::change_detection::Mut) or [`ResMut`](ResMut). -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct SystemChangeTick { last_run: Tick, this_run: Tick, @@ -1834,6 +1626,170 @@ unsafe impl SystemParam for SystemChangeTick { } } +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl SystemParam for Option { + type State = T::State; + + type Item<'world, 'state> = Option>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + #[inline] + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + T::validate_param(state, system_meta, world) + .ok() + .map(|()| T::get_param(state, system_meta, world, change_tick)) + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { + T::queue(state, system_meta, world); + } +} + +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl ReadOnlySystemParam for Option {} + +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl SystemParam for Result { + type State = T::State; + + type Item<'world, 'state> = Result, SystemParamValidationError>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + #[inline] + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + T::validate_param(state, system_meta, world) + .map(|()| T::get_param(state, system_meta, world, change_tick)) + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { + T::queue(state, system_meta, world); + } +} + +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl ReadOnlySystemParam for Result {} + +/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid. +/// +/// # Example +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Resource)] +/// # struct SomeResource; +/// // This system will fail if `SomeResource` is not present. +/// fn fails_on_missing_resource(res: Res) {} +/// +/// // This system will skip without error if `SomeResource` is not present. +/// fn skips_on_missing_resource(res: When>) { +/// // The inner parameter is available using `Deref` +/// let some_resource: &SomeResource = &res; +/// } +/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource); +/// ``` +#[derive(Debug)] +pub struct When(pub T); + +impl When { + /// Returns the inner `T`. + /// + /// The inner value is `pub`, so you can also obtain it by destructuring the parameter: + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct SomeResource; + /// fn skips_on_missing_resource(When(res): When>) { + /// let some_resource: Res = res; + /// } + /// # bevy_ecs::system::assert_is_system(skips_on_missing_resource); + /// ``` + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Deref for When { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for When { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl SystemParam for When { + type State = T::State; + + type Item<'world, 'state> = When>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + T::init_state(world, system_meta) + } + + #[inline] + unsafe fn validate_param( + state: &mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + T::validate_param(state, system_meta, world).map_err(|mut e| { + e.skipped = true; + e + }) + } + + #[inline] + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + change_tick: Tick, + ) -> Self::Item<'world, 'state> { + When(T::get_param(state, system_meta, world, change_tick)) + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + T::apply(state, system_meta, world); + } + + fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { + T::queue(state, system_meta, world); + } +} + +// SAFETY: Delegates to `T`, which ensures the safety requirements are met +unsafe impl ReadOnlySystemParam for When {} + // SAFETY: When initialized with `init_state`, `get_param` returns an empty `Vec` and does no access. // Therefore, `init_state` trivially registers all access, and no accesses can conflict. // Note that the safety requirements for non-empty `Vec`s are handled by the `SystemParamBuilder` impl that builds them. @@ -1848,13 +1804,14 @@ unsafe impl SystemParam for Vec { #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { - state - .iter() - .all(|state| T::validate_param(state, system_meta, world)) + ) -> Result<(), SystemParamValidationError> { + for state in state { + T::validate_param(state, system_meta, world)?; + } + Ok(()) } #[inline] @@ -1873,17 +1830,6 @@ unsafe impl SystemParam for Vec { .collect() } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - for state in state { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { T::new_archetype(state, archetype, system_meta) }; - } - } - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { for state in state { T::apply(state, system_meta, world); @@ -1924,17 +1870,6 @@ unsafe impl SystemParam for ParamSet<'_, '_, Vec> { } } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - for state in state { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { T::new_archetype(state, archetype, system_meta) } - } - } - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { for state in state { T::apply(state, system_meta, world); @@ -1999,7 +1934,7 @@ macro_rules! impl_system_param_tuple { reason = "Zero-length tuples won't use some of the parameters." )] $(#[$meta])* - // SAFETY: implementors of each `SystemParam` in the tuple have validated their impls + // SAFETY: implementers of each `SystemParam` in the tuple have validated their impls unsafe impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { type State = ($($param::State,)*); type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); @@ -2009,16 +1944,6 @@ macro_rules! impl_system_param_tuple { (($($param::init_state(world, system_meta),)*)) } - #[inline] - unsafe fn new_archetype(($($param,)*): &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) { - #[allow( - unused_unsafe, - reason = "Zero-length tuples will not run anything in the unsafe block." - )] - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { $($param::new_archetype($param, archetype, system_meta);)* } - } - #[inline] fn apply(($($param,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) { $($param::apply($param, system_meta, world);)* @@ -2035,12 +1960,15 @@ macro_rules! impl_system_param_tuple { #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { let ($($param,)*) = state; - $($param::validate_param($param, system_meta, world)&&)* true + $( + $param::validate_param($param, system_meta, world)?; + )* + Ok(()) } #[inline] @@ -2185,15 +2113,6 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, P::init_state(world, system_meta) } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: The caller guarantees that the provided `archetype` matches the World used to initialize `state`. - unsafe { P::new_archetype(state, archetype, system_meta) }; - } - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { P::apply(state, system_meta, world); } @@ -2204,10 +2123,10 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { P::validate_param(state, system_meta, world) } @@ -2430,16 +2349,7 @@ impl DynSystemParamState { } /// Allows a [`SystemParam::State`] to be used as a trait object for implementing [`DynSystemParam`]. -trait DynParamState: Sync + Send { - /// Casts the underlying `ParamState` to an `Any` so it can be downcast. - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable).a - /// - /// # Safety - /// `archetype` must be from the [`World`] used to initialize `state` in [`SystemParam::init_state`]. - unsafe fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta); - +trait DynParamState: Sync + Send + Any { /// Applies any deferred mutations stored in this [`SystemParam`]'s state. /// This is used to apply [`Commands`] during [`ApplyDeferred`](crate::prelude::ApplyDeferred). /// @@ -2453,22 +2363,17 @@ trait DynParamState: Sync + Send { /// /// # Safety /// Refer to [`SystemParam::validate_param`]. - unsafe fn validate_param(&self, system_meta: &SystemMeta, world: UnsafeWorldCell) -> bool; + unsafe fn validate_param( + &mut self, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError>; } /// A wrapper around a [`SystemParam::State`] that can be used as a trait object in a [`DynSystemParam`]. struct ParamState(T::State); impl DynParamState for ParamState { - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - unsafe fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { T::new_archetype(&mut self.0, archetype, system_meta) }; - } - fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { T::apply(&mut self.0, system_meta, world); } @@ -2477,8 +2382,12 @@ impl DynParamState for ParamState { T::queue(&mut self.0, system_meta, world); } - unsafe fn validate_param(&self, system_meta: &SystemMeta, world: UnsafeWorldCell) -> bool { - T::validate_param(&self.0, system_meta, world) + unsafe fn validate_param( + &mut self, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + T::validate_param(&mut self.0, system_meta, world) } } @@ -2494,10 +2403,10 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { state.0.validate_param(system_meta, world) } @@ -2509,27 +2418,11 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { change_tick: Tick, ) -> Self::Item<'world, 'state> { // SAFETY: - // - `state.0` is a boxed `ParamState`, and its implementation of `as_any_mut` returns `self`. + // - `state.0` is a boxed `ParamState`. // - The state was obtained from `SystemParamBuilder::build()`, which registers all [`World`] accesses used // by [`SystemParam::get_param`] with the provided [`system_meta`](SystemMeta). // - The caller ensures that the provided world is the same and has the required access. - unsafe { - DynSystemParam::new( - state.0.as_any_mut(), - world, - system_meta.clone(), - change_tick, - ) - } - } - - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { state.0.new_archetype(archetype, system_meta) }; + unsafe { DynSystemParam::new(state.0.as_mut(), world, system_meta.clone(), change_tick) } } fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { @@ -2592,6 +2485,82 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { } } +/// An error that occurs when a system parameter is not valid, +/// used by system executors to determine what to do with a system. +/// +/// Returned as an error from [`SystemParam::validate_param`], +/// and handled using the unified error handling mechanisms defined in [`bevy_ecs::error`]. +#[derive(Debug, PartialEq, Eq, Clone, Error)] +pub struct SystemParamValidationError { + /// Whether the system should be skipped. + /// + /// If `false`, the error should be handled. + /// By default, this will result in a panic. See [`crate::error`] for more information. + /// + /// This is the default behavior, and is suitable for system params that should *always* be valid, + /// either because sensible fallback behavior exists (like [`Query`]) or because + /// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]). + /// + /// If `true`, the system should be skipped. + /// This is set by wrapping the system param in [`When`], + /// and indicates that the system is intended to only operate in certain application states. + pub skipped: bool, + + /// A message describing the validation error. + pub message: Cow<'static, str>, + + /// A string identifying the invalid parameter. + /// This is usually the type name of the parameter. + pub param: Cow<'static, str>, + + /// A string identifying the field within a parameter using `#[derive(SystemParam)]`. + /// This will be an empty string for other parameters. + /// + /// This will be printed after `param` in the `Display` impl, and should include a `::` prefix if non-empty. + pub field: Cow<'static, str>, +} + +impl SystemParamValidationError { + /// Constructs a `SystemParamValidationError` that skips the system. + /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. + pub fn skipped(message: impl Into>) -> Self { + Self::new::(true, message, Cow::Borrowed("")) + } + + /// Constructs a `SystemParamValidationError` for an invalid parameter that should be treated as an error. + /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. + pub fn invalid(message: impl Into>) -> Self { + Self::new::(false, message, Cow::Borrowed("")) + } + + /// Constructs a `SystemParamValidationError` for an invalid parameter. + /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. + pub fn new( + skipped: bool, + message: impl Into>, + field: impl Into>, + ) -> Self { + Self { + skipped, + message: message.into(), + param: Cow::Borrowed(core::any::type_name::()), + field: field.into(), + } + } +} + +impl Display for SystemParamValidationError { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!( + fmt, + "Parameter `{}{}` failed validation: {}", + ShortName(&self.param), + self.field, + self.message + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -2825,4 +2794,34 @@ mod tests { let _query: Query<()> = p.downcast_mut_inner().unwrap(); let _query: Query<()> = p.downcast().unwrap(); } + + #[test] + #[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_resource_error::res_system`: Parameter `Res` failed validation: Resource does not exist"] + fn missing_resource_error() { + #[derive(Resource)] + pub struct MissingResource; + + let mut schedule = crate::schedule::Schedule::default(); + schedule.add_systems(res_system); + let mut world = World::new(); + schedule.run(&mut world); + + fn res_system(_: Res) {} + } + + #[test] + #[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_event_error::event_system`: Parameter `EventReader::events` failed validation: Event not initialized"] + fn missing_event_error() { + use crate::prelude::{Event, EventReader}; + + #[derive(Event)] + pub struct MissingEvent; + + let mut schedule = crate::schedule::Schedule::default(); + schedule.add_systems(event_system); + let mut world = World::new(); + schedule.run(&mut world); + + fn event_system(_: EventReader) {} + } } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 11d74beca5..cf53b35be5 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -3,7 +3,7 @@ use crate::reflect::ReflectComponent; use crate::{ change_detection::Mut, entity::Entity, - system::{input::SystemInput, BoxedSystem, IntoSystem}, + system::{input::SystemInput, BoxedSystem, IntoSystem, SystemParamValidationError}, world::World, }; use alloc::boxed::Box; @@ -351,15 +351,16 @@ impl World { initialized = true; } - let result = if system.validate_param(self) { - // Wait to run the commands until the system is available again. - // This is needed so the systems can recursively run themselves. - let ret = system.run_without_applying_deferred(input, self); - system.queue_deferred(self.into()); - Ok(ret) - } else { - Err(RegisteredSystemError::InvalidParams(id)) - }; + let result = system + .validate_param(self) + .map_err(|err| RegisteredSystemError::InvalidParams { system: id, err }) + .map(|()| { + // Wait to run the commands until the system is available again. + // This is needed so the systems can recursively run themselves. + let ret = system.run_without_applying_deferred(input, self); + system.queue_deferred(self.into()); + ret + }); // Return ownership of system trait object (if entity still exists) if let Ok(mut entity) = self.get_entity_mut(id.entity) { @@ -492,10 +493,14 @@ pub enum RegisteredSystemError { #[error("System {0:?} tried to remove itself")] SelfRemove(SystemId), /// System could not be run due to parameters that failed validation. - /// - /// This can occur because the data required by the system was not present in the world. - #[error("The data required by the system {0:?} was not found in the world and the system did not run due to failed parameter validation.")] - InvalidParams(SystemId), + /// This should not be considered an error if [`field@SystemParamValidationError::skipped`] is `true`. + #[error("System {system:?} did not run due to failed parameter validation: {err}")] + InvalidParams { + /// The identifier of the system that was run. + system: SystemId, + /// The returned parameter validation error. + err: SystemParamValidationError, + }, } impl core::fmt::Debug for RegisteredSystemError { @@ -507,7 +512,11 @@ impl core::fmt::Debug for RegisteredSystemError { Self::SystemNotCached => write!(f, "SystemNotCached"), Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(), Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(), - Self::InvalidParams(arg0) => f.debug_tuple("InvalidParams").field(arg0).finish(), + Self::InvalidParams { system, err } => f + .debug_struct("InvalidParams") + .field("system", system) + .field("err", err) + .finish(), } } } @@ -856,20 +865,23 @@ mod tests { #[test] fn run_system_invalid_params() { use crate::system::RegisteredSystemError; + use alloc::{format, string::ToString}; struct T; impl Resource for T {} fn system(_: Res) {} let mut world = World::new(); - let id = world.register_system(system.warn_param_missing()); + let id = world.register_system(system); // This fails because `T` has not been added to the world yet. let result = world.run_system(id); assert!(matches!( result, - Err(RegisteredSystemError::InvalidParams(_)) + Err(RegisteredSystemError::InvalidParams { .. }) )); + let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res` failed validation: Resource does not exist"); + assert_eq!(expected, result.unwrap_err().to_string()); } #[test] @@ -894,4 +906,13 @@ mod tests { assert_eq!(INVOCATIONS_LEFT.get(), 0); } + + #[test] + fn run_system_exclusive_adapters() { + let mut world = World::new(); + fn system(_: &mut World) {} + world.run_system_cached(system).unwrap(); + world.run_system_cached(system.pipe(system)).unwrap(); + world.run_system_cached(system.map(|()| {})).unwrap(); + } } diff --git a/crates/bevy_ecs/src/traversal.rs b/crates/bevy_ecs/src/traversal.rs index 342ad47849..306ae7c92d 100644 --- a/crates/bevy_ecs/src/traversal.rs +++ b/crates/bevy_ecs/src/traversal.rs @@ -10,7 +10,7 @@ use crate::{entity::Entity, query::ReadOnlyQueryData, relationship::Relationship /// Infinite loops are possible, and are not checked for. While looping can be desirable in some contexts /// (for example, an observer that triggers itself multiple times before stopping), following an infinite /// traversal loop without an eventual exit will cause your application to hang. Each implementer of `Traversal` -/// for documenting possible looping behavior, and consumers of those implementations are responsible for +/// is responsible for documenting possible looping behavior, and consumers of those implementations are responsible for /// avoiding infinite loops in their code. /// /// Traversals may be parameterized with additional data. For example, in observer event propagation, the diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 22474a75fe..02c12fe6a3 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -20,6 +20,8 @@ use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLAC /// A [`World`] reference that disallows structural ECS changes. /// This includes initializing resources, registering components or spawning entities. +/// +/// This means that in order to add entities, for example, you will need to use commands instead of the world directly. pub struct DeferredWorld<'w> { // SAFETY: Implementors must not use this reference to make structural changes world: UnsafeWorldCell<'w>, @@ -101,9 +103,38 @@ impl<'w> DeferredWorld<'w> { return Ok(None); }; + self.modify_component_by_id(entity, component_id, move |component| { + // SAFETY: component matches the component_id collected in the above line + let mut component = unsafe { component.with_type::() }; + + f(&mut component) + }) + } + + /// Temporarily removes a [`Component`] identified by the provided + /// [`ComponentId`] from the provided [`Entity`] and runs the provided + /// closure on it, returning the result if the component was available. + /// This will trigger the `OnRemove` and `OnReplace` component hooks without + /// causing an archetype move. + /// + /// This is most useful with immutable components, where removal and reinsertion + /// is the only way to modify a value. + /// + /// If you do not need to ensure the above hooks are triggered, and your component + /// is mutable, prefer using [`get_mut_by_id`](DeferredWorld::get_mut_by_id). + /// + /// You should prefer the typed [`modify_component`](DeferredWorld::modify_component) + /// whenever possible. + #[inline] + pub(crate) fn modify_component_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + f: impl for<'a> FnOnce(MutUntyped<'a>) -> R, + ) -> Result, EntityMutableFetchError> { let entity_cell = self.get_entity_mut(entity)?; - if !entity_cell.contains::() { + if !entity_cell.contains_id(component_id) { return Ok(None); } @@ -140,11 +171,11 @@ impl<'w> DeferredWorld<'w> { // SAFETY: we will run the required hooks to simulate removal/replacement. let mut component = unsafe { entity_cell - .get_mut_assume_mutable::() + .get_mut_assume_mutable_by_id(component_id) .expect("component access confirmed above") }; - let result = f(&mut component); + let result = f(component.reborrow()); // Simulate adding this component by updating the relevant ticks *component.ticks.added = *component.ticks.changed; @@ -202,8 +233,8 @@ impl<'w> DeferredWorld<'w> { /// For examples, see [`DeferredWorld::entity_mut`]. /// /// [`EntityMut`]: crate::world::EntityMut - /// [`&EntityHashSet`]: crate::entity::hash_set::EntityHashSet - /// [`EntityHashMap`]: crate::entity::hash_map::EntityHashMap + /// [`&EntityHashSet`]: crate::entity::EntityHashSet + /// [`EntityHashMap`]: crate::entity::EntityHashMap /// [`Vec`]: alloc::vec::Vec #[inline] pub fn get_entity_mut( @@ -311,7 +342,7 @@ impl<'w> DeferredWorld<'w> { /// ## [`&EntityHashSet`] /// /// ``` - /// # use bevy_ecs::{prelude::*, entity::hash_set::EntityHashSet, world::DeferredWorld}; + /// # use bevy_ecs::{prelude::*, entity::EntityHashSet, world::DeferredWorld}; /// #[derive(Component)] /// struct Position { /// x: f32, @@ -334,8 +365,8 @@ impl<'w> DeferredWorld<'w> { /// ``` /// /// [`EntityMut`]: crate::world::EntityMut - /// [`&EntityHashSet`]: crate::entity::hash_set::EntityHashSet - /// [`EntityHashMap`]: crate::entity::hash_map::EntityHashMap + /// [`&EntityHashSet`]: crate::entity::EntityHashSet + /// [`EntityHashMap`]: crate::entity::EntityHashMap /// [`Vec`]: alloc::vec::Vec #[inline] pub fn entity_mut(&mut self, entities: F) -> F::DeferredMut<'_> { diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index b14e42cf01..8588131563 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem::MaybeUninit; use crate::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity, EntityDoesNotExistError}, + entity::{Entity, EntityDoesNotExistError, EntityHashMap, EntityHashSet}, error::Result, world::{ error::EntityMutableFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, @@ -39,8 +39,8 @@ impl<'w> EntityFetcher<'w> { /// - Pass an [`Entity`] to receive a single [`EntityRef`]. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityRef`]s. - /// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an - /// [`EntityHashMap`](crate::entity::hash_map::EntityHashMap). + /// - Pass a reference to a [`EntityHashSet`](crate::entity::EntityHashMap) to receive an + /// [`EntityHashMap`](crate::entity::EntityHashMap). /// /// # Errors /// @@ -71,8 +71,8 @@ impl<'w> EntityFetcher<'w> { /// such as adding or removing components, or despawning the entity. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityMut`]s. - /// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an - /// [`EntityHashMap`](crate::entity::hash_map::EntityHashMap). + /// - Pass a reference to a [`EntityHashSet`](crate::entity::EntityHashMap) to receive an + /// [`EntityHashMap`](crate::entity::EntityHashMap). /// # Errors /// /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 3813d34745..aa3e66ca14 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,33 +1,30 @@ use crate::{ - archetype::{Archetype, ArchetypeId, Archetypes}, + archetype::{Archetype, ArchetypeId}, bundle::{ - Bundle, BundleEffect, BundleFromComponents, BundleId, BundleInfo, BundleInserter, - DynamicBundle, InsertMode, + Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle, + InsertMode, }, change_detection::{MaybeLocation, MutUntyped}, component::{ Component, ComponentId, ComponentTicks, Components, ComponentsRegistrator, Mutable, - StorageType, + StorageType, Tick, }, entity::{ - Entities, Entity, EntityBorrow, EntityCloner, EntityClonerBuilder, EntityLocation, - TrustedEntityBorrow, + ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, EntityLocation, }, event::Event, observer::Observer, - query::{Access, ReadOnlyQueryData}, + query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData}, relationship::RelationshipHookMode, - removal_detection::RemovedComponentEvents, resource::Resource, - storage::Storages, system::IntoObserverSystem, world::{ - error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, DeferredWorld, Mut, Ref, - World, ON_DESPAWN, ON_REMOVE, ON_REPLACE, + error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World, + ON_DESPAWN, ON_REMOVE, ON_REPLACE, }, }; use alloc::vec::Vec; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_ptr::{OwningPtr, Ptr}; use core::{ any::TypeId, @@ -250,7 +247,7 @@ impl<'w> EntityRef<'w> { /// ## [`HashSet`] of [`ComponentId`]s /// /// ``` - /// # use bevy_platform_support::collections::HashSet; + /// # use bevy_platform::collections::HashSet; /// # use bevy_ecs::{prelude::*, component::ComponentId}; /// # /// # #[derive(Component, PartialEq, Debug)] @@ -299,6 +296,11 @@ impl<'w> EntityRef<'w> { pub fn spawned_by(&self) -> MaybeLocation { self.cell.spawned_by() } + + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.cell.spawned_at() + } } impl<'w> From> for EntityRef<'w> { @@ -415,14 +417,14 @@ impl Hash for EntityRef<'_> { } } -impl EntityBorrow for EntityRef<'_> { +impl ContainsEntity for EntityRef<'_> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for EntityRef<'_> {} +unsafe impl EntityEquivalent for EntityRef<'_> {} /// Provides mutable access to a single entity and all of its components. /// @@ -792,7 +794,7 @@ impl<'w> EntityMut<'w> { /// ## [`HashSet`] of [`ComponentId`]s /// /// ``` - /// # use bevy_platform_support::collections::HashSet; + /// # use bevy_platform::collections::HashSet; /// # use bevy_ecs::{prelude::*, component::ComponentId}; /// # /// # #[derive(Component, PartialEq, Debug)] @@ -824,6 +826,39 @@ impl<'w> EntityMut<'w> { unsafe { component_ids.fetch_mut(self.cell) } } + /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for + /// the current entity, based on the given [`ComponentId`]s. + /// Assumes the given [`ComponentId`]s refer to mutable components. + /// + /// **You should prefer to use the typed API [`EntityMut::get_mut_assume_mutable`] where + /// possible and only use this in cases where the actual component types + /// are not known at compile time.** + /// + /// Unlike [`EntityMut::get_mut_assume_mutable`], this returns untyped reference(s) to + /// component(s), and it's the job of the caller to ensure the correct + /// type(s) are dereferenced (if necessary). + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if the entity does + /// not have a component. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component + /// is requested multiple times. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the provided [`ComponentId`]s must refer to mutable components. + #[inline] + pub unsafe fn get_mut_assume_mutable_by_id( + &mut self, + component_ids: F, + ) -> Result, EntityComponentError> { + // SAFETY: + // - `&mut self` ensures that no references exist to this entity's components. + // - We have exclusive access to all components of this entity. + unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } + } + /// Returns [untyped mutable reference](MutUntyped) to component for /// the current entity, based on the given [`ComponentId`]. /// @@ -852,6 +887,36 @@ impl<'w> EntityMut<'w> { unsafe { component_ids.fetch_mut(self.cell) } } + /// Returns [untyped mutable reference](MutUntyped) to component for + /// the current entity, based on the given [`ComponentId`]. + /// Assumes the given [`ComponentId`]s refer to mutable components. + /// + /// Unlike [`EntityMut::get_mut_assume_mutable_by_id`], this method borrows &self instead of + /// &mut self, allowing the caller to access multiple components simultaneously. + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if the entity does + /// not have a component. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component + /// is requested multiple times. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the component mutably + /// - no other references to the component exist at the same time + /// - the provided [`ComponentId`]s must refer to mutable components. + #[inline] + pub unsafe fn get_mut_assume_mutable_by_id_unchecked( + &self, + component_ids: F, + ) -> Result, EntityComponentError> { + // SAFETY: + // - The caller must ensure simultaneous access is limited + // - to components that are mutually independent. + unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } + } + /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) /// to component(s) with lifetime `'w` for the current entity, based on the /// given [`ComponentId`]s. @@ -885,10 +950,49 @@ impl<'w> EntityMut<'w> { unsafe { component_ids.fetch_mut(self.cell) } } + /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) + /// to component(s) with lifetime `'w` for the current entity, based on the + /// given [`ComponentId`]s. + /// Assumes the given [`ComponentId`]s refer to mutable components. + /// + /// **You should prefer to use the typed API [`EntityMut::into_mut_assume_mutable`] where + /// possible and only use this in cases where the actual component types + /// are not known at compile time.** + /// + /// Unlike [`EntityMut::into_mut_assume_mutable`], this returns untyped reference(s) to + /// component(s), and it's the job of the caller to ensure the correct + /// type(s) are dereferenced (if necessary). + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if the entity does + /// not have a component. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component + /// is requested multiple times. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the provided [`ComponentId`]s must refer to mutable components. + #[inline] + pub unsafe fn into_mut_assume_mutable_by_id( + self, + component_ids: F, + ) -> Result, EntityComponentError> { + // SAFETY: + // - consuming `self` ensures that no references exist to this entity's components. + // - We have exclusive access to all components of this entity. + unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } + } + /// Returns the source code location from which this entity has been spawned. pub fn spawned_by(&self) -> MaybeLocation { self.cell.spawned_by() } + + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.cell.spawned_at() + } } impl<'w> From<&'w mut EntityMut<'_>> for EntityMut<'w> { @@ -969,14 +1073,14 @@ impl Hash for EntityMut<'_> { } } -impl EntityBorrow for EntityMut<'_> { +impl ContainsEntity for EntityMut<'_> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for EntityMut<'_> {} +unsafe impl EntityEquivalent for EntityMut<'_> {} /// A mutable reference to a particular [`Entity`], and the entire world. /// @@ -1019,26 +1123,38 @@ impl<'w> EntityWorldMut<'w> { fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> { self.assert_not_despawned(); + let last_change_tick = self.world.last_change_tick; + let change_tick = self.world.read_change_tick(); UnsafeEntityCell::new( self.world.as_unsafe_world_cell_readonly(), self.entity, self.location, + last_change_tick, + change_tick, ) } fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { self.assert_not_despawned(); + let last_change_tick = self.world.last_change_tick; + let change_tick = self.world.change_tick(); UnsafeEntityCell::new( self.world.as_unsafe_world_cell(), self.entity, self.location, + last_change_tick, + change_tick, ) } fn into_unsafe_entity_cell(self) -> UnsafeEntityCell<'w> { self.assert_not_despawned(); + let last_change_tick = self.world.last_change_tick; + let change_tick = self.world.change_tick(); UnsafeEntityCell::new( self.world.as_unsafe_world_cell(), self.entity, self.location, + last_change_tick, + change_tick, ) } @@ -1302,6 +1418,38 @@ impl<'w> EntityWorldMut<'w> { Some(result) } + /// Temporarily removes a [`Component`] `T` from this [`Entity`] and runs the + /// provided closure on it, returning the result if `T` was available. + /// This will trigger the `OnRemove` and `OnReplace` component hooks without + /// causing an archetype move. + /// + /// This is most useful with immutable components, where removal and reinsertion + /// is the only way to modify a value. + /// + /// If you do not need to ensure the above hooks are triggered, and your component + /// is mutable, prefer using [`get_mut`](EntityWorldMut::get_mut). + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[inline] + pub fn modify_component_by_id( + &mut self, + component_id: ComponentId, + f: impl for<'a> FnOnce(MutUntyped<'a>) -> R, + ) -> Option { + self.assert_not_despawned(); + + let result = self + .world + .modify_component_by_id(self.entity, component_id, f) + .expect("entity access must be valid")?; + + self.update_location(); + + Some(result) + } + /// Gets mutable access to the component of type `T` for the current entity. /// Returns `None` if the entity does not have a component of type `T`. /// @@ -1326,6 +1474,23 @@ impl<'w> EntityWorldMut<'w> { unsafe { self.into_unsafe_entity_cell().get_mut() } } + /// Consumes `self` and gets mutable access to the component of type `T` + /// with the world `'w` lifetime for the current entity. + /// Returns `None` if the entity does not have a component of type `T`. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + /// + /// # Safety + /// + /// - `T` must be a mutable component + #[inline] + pub unsafe fn into_mut_assume_mutable(self) -> Option> { + // SAFETY: consuming `self` implies exclusive access + unsafe { self.into_unsafe_entity_cell().get_mut_assume_mutable() } + } + /// Gets a reference to the resource of the given type /// /// # Panics @@ -1487,6 +1652,41 @@ impl<'w> EntityWorldMut<'w> { self.as_mutable().into_mut_by_id(component_ids) } + /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for + /// the current entity, based on the given [`ComponentId`]s. + /// Assumes the given [`ComponentId`]s refer to mutable components. + /// + /// **You should prefer to use the typed API [`EntityWorldMut::get_mut_assume_mutable`] where + /// possible and only use this in cases where the actual component types + /// are not known at compile time.** + /// + /// Unlike [`EntityWorldMut::get_mut_assume_mutable`], this returns untyped reference(s) to + /// component(s), and it's the job of the caller to ensure the correct + /// type(s) are dereferenced (if necessary). + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if the entity does + /// not have a component. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component + /// is requested multiple times. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the provided [`ComponentId`]s must refer to mutable components. + #[inline] + pub unsafe fn get_mut_assume_mutable_by_id( + &mut self, + component_ids: F, + ) -> Result, EntityComponentError> { + self.as_mutable() + .into_mut_assume_mutable_by_id(component_ids) + } + /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) /// to component(s) with lifetime `'w` for the current entity, based on the /// given [`ComponentId`]s. @@ -1521,6 +1721,42 @@ impl<'w> EntityWorldMut<'w> { self.into_mutable().into_mut_by_id(component_ids) } + /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) + /// to component(s) with lifetime `'w` for the current entity, based on the + /// given [`ComponentId`]s. + /// Assumes the given [`ComponentId`]s refer to mutable components. + /// + /// **You should prefer to use the typed API [`EntityWorldMut::into_mut_assume_mutable`] where + /// possible and only use this in cases where the actual component types + /// are not known at compile time.** + /// + /// Unlike [`EntityWorldMut::into_mut_assume_mutable`], this returns untyped reference(s) to + /// component(s), and it's the job of the caller to ensure the correct + /// type(s) are dereferenced (if necessary). + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if the entity does + /// not have a component. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component + /// is requested multiple times. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the provided [`ComponentId`]s must refer to mutable components. + #[inline] + pub unsafe fn into_mut_assume_mutable_by_id( + self, + component_ids: F, + ) -> Result, EntityComponentError> { + self.into_mutable() + .into_mut_assume_mutable_by_id(component_ids) + } + /// Adds a [`Bundle`] of components to the entity. /// /// This will overwrite any previous value(s) of the same component type. @@ -1761,281 +1997,57 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. - // TODO: BundleRemover? #[must_use] #[track_caller] pub fn take(&mut self) -> Option { self.assert_not_despawned(); - let world = &mut self.world; - let storages = &mut world.storages; - // SAFETY: These come from the same world. - let mut registrator = - unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) }; - let bundle_id = world.bundles.register_info::(&mut registrator, storages); - // SAFETY: We just ensured this bundle exists - let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) }; - let old_location = self.location; - // SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid, - // components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T` - let new_archetype_id = unsafe { - bundle_info.remove_bundle_from_archetype( - &mut world.archetypes, - storages, - ®istrator, - &world.observers, - old_location.archetype_id, - false, - )? - }; - - if new_archetype_id == old_location.archetype_id { - return None; - } - let entity = self.entity; - // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld - let (old_archetype, bundle_info, mut deferred_world) = unsafe { - let bundle_info: *const BundleInfo = bundle_info; - let world = world.as_unsafe_world_cell(); - ( - &world.archetypes()[old_location.archetype_id], - &*bundle_info, - world.into_deferred(), + let location = self.location; + + let mut remover = + // SAFETY: The archetype id must be valid since this entity is in it. + unsafe { BundleRemover::new::(self.world, self.location.archetype_id, true) }?; + // SAFETY: The passed location has the sane archetype as the remover, since they came from the same location. + let (new_location, result) = unsafe { + remover.remove( + entity, + location, + MaybeLocation::caller(), + |sets, table, components, bundle_components| { + let mut bundle_components = bundle_components.iter().copied(); + ( + false, + T::from_components(&mut (sets, table), &mut |(sets, table)| { + let component_id = bundle_components.next().unwrap(); + // SAFETY: the component existed to be removed, so its id must be valid. + let component_info = components.get_info_unchecked(component_id); + match component_info.storage_type() { + StorageType::Table => { + table + .as_mut() + // SAFETY: The table must be valid if the component is in it. + .debug_checked_unwrap() + // SAFETY: The remover is cleaning this up. + .take_component(component_id, location.table_row) + } + StorageType::SparseSet => sets + .get_mut(component_id) + .unwrap() + .remove_and_forget(entity) + .unwrap(), + } + }), + ) + }, ) }; + self.location = new_location; - // SAFETY: all bundle components exist in World - unsafe { - trigger_on_replace_and_on_remove_hooks_and_observers( - &mut deferred_world, - old_archetype, - entity, - bundle_info, - MaybeLocation::caller(), - ); - } - - let archetypes = &mut world.archetypes; - let storages = &mut world.storages; - let components = &mut world.components; - let entities = &mut world.entities; - let removed_components = &mut world.removed_components; - - let entity = self.entity; - let mut bundle_components = bundle_info.iter_explicit_components(); - // SAFETY: bundle components are iterated in order, which guarantees that the component type - // matches - let result = unsafe { - T::from_components(storages, &mut |storages| { - let component_id = bundle_components.next().unwrap(); - // SAFETY: - // - entity location is valid - // - table row is removed below, without dropping the contents - // - `components` comes from the same world as `storages` - // - the component exists on the entity - take_component( - storages, - components, - removed_components, - component_id, - entity, - old_location, - ) - }) - }; - - #[expect( - clippy::undocumented_unsafe_blocks, - reason = "Needs to be documented; see #17345." - )] - unsafe { - Self::move_entity_from_remove::( - entity, - &mut self.location, - old_location.archetype_id, - old_location, - entities, - archetypes, - storages, - new_archetype_id, - ); - } self.world.flush(); self.update_location(); Some(result) } - /// # Safety - /// - /// `new_archetype_id` must have the same or a subset of the components - /// in `old_archetype_id`. Probably more safety stuff too, audit a call to - /// this fn as if the code here was written inline - /// - /// when DROP is true removed components will be dropped otherwise they will be forgotten - // We use a const generic here so that we are less reliant on - // inlining for rustc to optimize out the `match DROP` - unsafe fn move_entity_from_remove( - entity: Entity, - self_location: &mut EntityLocation, - old_archetype_id: ArchetypeId, - old_location: EntityLocation, - entities: &mut Entities, - archetypes: &mut Archetypes, - storages: &mut Storages, - new_archetype_id: ArchetypeId, - ) { - let old_archetype = &mut archetypes[old_archetype_id]; - let remove_result = old_archetype.swap_remove(old_location.archetype_row); - // if an entity was moved into this entity's archetype row, update its archetype row - if let Some(swapped_entity) = remove_result.swapped_entity { - let swapped_location = entities.get(swapped_entity).unwrap(); - - entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: old_location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); - } - let old_table_row = remove_result.table_row; - let old_table_id = old_archetype.table_id(); - let new_archetype = &mut archetypes[new_archetype_id]; - - let new_location = if old_table_id == new_archetype.table_id() { - new_archetype.allocate(entity, old_table_row) - } else { - let (old_table, new_table) = storages - .tables - .get_2_mut(old_table_id, new_archetype.table_id()); - - let move_result = if DROP { - // SAFETY: old_table_row exists - unsafe { old_table.move_to_and_drop_missing_unchecked(old_table_row, new_table) } - } else { - // SAFETY: old_table_row exists - unsafe { old_table.move_to_and_forget_missing_unchecked(old_table_row, new_table) } - }; - - // SAFETY: move_result.new_row is a valid position in new_archetype's table - let new_location = unsafe { new_archetype.allocate(entity, move_result.new_row) }; - - // if an entity was moved into this entity's table row, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = entities.get(swapped_entity).unwrap(); - - entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: swapped_location.archetype_row, - table_id: swapped_location.table_id, - table_row: old_location.table_row, - }, - ); - archetypes[swapped_location.archetype_id] - .set_entity_table_row(swapped_location.archetype_row, old_table_row); - } - - new_location - }; - - *self_location = new_location; - // SAFETY: The entity is valid and has been moved to the new location already. - unsafe { - entities.set(entity.index(), new_location); - } - } - - /// Remove the components of `bundle` from `entity`. - /// - /// # Safety - /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. - unsafe fn remove_bundle(&mut self, bundle: BundleId, caller: MaybeLocation) -> EntityLocation { - let entity = self.entity; - let world = &mut self.world; - let location = self.location; - // SAFETY: the caller guarantees that the BundleInfo for this id has been initialized. - let bundle_info = world.bundles.get_unchecked(bundle); - - // SAFETY: `archetype_id` exists because it is referenced in `location` which is valid - // and components in `bundle_info` must exist due to this function's safety invariants. - let new_archetype_id = bundle_info - .remove_bundle_from_archetype( - &mut world.archetypes, - &mut world.storages, - &world.components, - &world.observers, - location.archetype_id, - // components from the bundle that are not present on the entity are ignored - true, - ) - .expect("intersections should always return a result"); - - if new_archetype_id == location.archetype_id { - return location; - } - - // SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld - let (old_archetype, bundle_info, mut deferred_world) = unsafe { - let bundle_info: *const BundleInfo = bundle_info; - let world = world.as_unsafe_world_cell(); - ( - &world.archetypes()[location.archetype_id], - &*bundle_info, - world.into_deferred(), - ) - }; - - // SAFETY: all bundle components exist in World - unsafe { - trigger_on_replace_and_on_remove_hooks_and_observers( - &mut deferred_world, - old_archetype, - entity, - bundle_info, - caller, - ); - } - - let old_archetype = &world.archetypes[location.archetype_id]; - for component_id in bundle_info.iter_explicit_components() { - if old_archetype.contains(component_id) { - world.removed_components.send(component_id, entity); - - // Make sure to drop components stored in sparse sets. - // Dense components are dropped later in `move_to_and_drop_missing_unchecked`. - if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) { - world - .storages - .sparse_sets - .get_mut(component_id) - // Set exists because the component existed on the entity - .unwrap() - .remove(entity); - } - } - } - - // SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id` - // because it is created by removing a bundle from these components. - let mut new_location = location; - Self::move_entity_from_remove::( - entity, - &mut new_location, - location.archetype_id, - location, - &mut world.entities, - &mut world.archetypes, - &mut world.storages, - new_archetype_id, - ); - - new_location - } - /// Removes any components in the [`Bundle`] from the entity. /// /// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details. @@ -2043,7 +2055,6 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. - // TODO: BundleRemover? #[track_caller] pub fn remove(&mut self) -> &mut Self { self.remove_with_caller::(MaybeLocation::caller()) @@ -2052,18 +2063,25 @@ impl<'w> EntityWorldMut<'w> { #[inline] pub(crate) fn remove_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { self.assert_not_despawned(); - let storages = &mut self.world.storages; - // SAFETY: These come from the same world. - let mut registrator = unsafe { - ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids) - }; - let bundle_info = self - .world - .bundles - .register_info::(&mut registrator, storages); - // SAFETY: the `BundleInfo` is initialized above - self.location = unsafe { self.remove_bundle(bundle_info, caller) }; + let Some(mut remover) = + // SAFETY: The archetype id must be valid since this entity is in it. + (unsafe { BundleRemover::new::(self.world, self.location.archetype_id, false) }) + else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + caller, + BundleRemover::empty_pre_remove, + ) + } + .0; + + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2085,16 +2103,31 @@ impl<'w> EntityWorldMut<'w> { ) -> &mut Self { self.assert_not_despawned(); let storages = &mut self.world.storages; + let bundles = &mut self.world.bundles; // SAFETY: These come from the same world. let mut registrator = unsafe { ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids) }; - let bundles = &mut self.world.bundles; - let bundle_id = bundles.register_contributed_bundle_info::(&mut registrator, storages); - // SAFETY: the dynamic `BundleInfo` is initialized above - self.location = unsafe { self.remove_bundle(bundle_id, caller) }; + // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. + let Some(mut remover) = (unsafe { + BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) + }) else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + caller, + BundleRemover::empty_pre_remove, + ) + } + .0; + + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2141,8 +2174,24 @@ impl<'w> EntityWorldMut<'w> { .bundles .init_dynamic_info(&mut self.world.storages, ®istrator, to_remove); - // SAFETY: the `BundleInfo` for the components to remove is initialized above - self.location = unsafe { self.remove_bundle(remove_bundle, caller) }; + // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. + let Some(mut remover) = (unsafe { + BundleRemover::new_with_id(self.world, self.location.archetype_id, remove_bundle, false) + }) else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + caller, + BundleRemover::empty_pre_remove, + ) + } + .0; + + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2176,8 +2225,24 @@ impl<'w> EntityWorldMut<'w> { component_id, ); - // SAFETY: the `BundleInfo` for this `component_id` is initialized above - self.location = unsafe { self.remove_bundle(bundle_id, caller) }; + // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. + let Some(mut remover) = (unsafe { + BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) + }) else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + caller, + BundleRemover::empty_pre_remove, + ) + } + .0; + + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2202,9 +2267,24 @@ impl<'w> EntityWorldMut<'w> { component_ids, ); - // SAFETY: the `BundleInfo` for this `bundle_id` is initialized above - unsafe { self.remove_bundle(bundle_id, MaybeLocation::caller()) }; + // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. + let Some(mut remover) = (unsafe { + BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) + }) else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + MaybeLocation::caller(), + BundleRemover::empty_pre_remove, + ) + } + .0; + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2232,8 +2312,24 @@ impl<'w> EntityWorldMut<'w> { component_ids.as_slice(), ); - // SAFETY: the `BundleInfo` for this `component_id` is initialized above - self.location = unsafe { self.remove_bundle(bundle_id, caller) }; + // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. + let Some(mut remover) = (unsafe { + BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) + }) else { + return self; + }; + // SAFETY: The remover archetype came from the passed location and the removal can not fail. + let new_location = unsafe { + remover.remove( + self.entity, + self.location, + caller, + BundleRemover::empty_pre_remove, + ) + } + .0; + + self.location = new_location; self.world.flush(); self.update_location(); self @@ -2256,15 +2352,6 @@ impl<'w> EntityWorldMut<'w> { self.despawn_with_caller(MaybeLocation::caller()); } - /// Despawns the provided entity and its descendants. - #[deprecated( - since = "0.16.0", - note = "Use entity.despawn(), which now automatically despawns recursively." - )] - pub fn despawn_recursive(self) { - self.despawn(); - } - pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { self.assert_not_despawned(); let world = self.world; @@ -2338,6 +2425,7 @@ impl<'w> EntityWorldMut<'w> { .expect("entity should exist at this point."); let table_row; let moved_entity; + let change_tick = world.change_tick(); { let archetype = &mut world.archetypes[self.location.archetype_id]; @@ -2356,6 +2444,9 @@ impl<'w> EntityWorldMut<'w> { table_row: swapped_location.table_row, }, ); + world + .entities + .mark_spawn_despawn(swapped_entity.index(), caller, change_tick); } } table_row = remove_result.table_row; @@ -2385,18 +2476,14 @@ impl<'w> EntityWorldMut<'w> { table_row, }, ); + world + .entities + .mark_spawn_despawn(moved_entity.index(), caller, change_tick); } world.archetypes[moved_location.archetype_id] .set_entity_table_row(moved_location.archetype_row, table_row); } world.flush(); - - // SAFETY: No structural changes - unsafe { - world - .entities_mut() - .set_spawned_or_despawned_by(self.entity.index(), caller); - } } /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] @@ -2556,6 +2643,8 @@ impl<'w> EntityWorldMut<'w> { /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. + /// + /// Panics if the given system is an exclusive system. #[track_caller] pub fn observe( &mut self, @@ -2735,46 +2824,35 @@ impl<'w> EntityWorldMut<'w> { .entity_get_spawned_or_despawned_by(self.entity) .map(|location| location.unwrap()) } -} -/// # Safety -/// All components in the archetype must exist in world -unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( - deferred_world: &mut DeferredWorld, - archetype: &Archetype, - entity: Entity, - bundle_info: &BundleInfo, - caller: MaybeLocation, -) { - let bundle_components_in_archetype = || { - bundle_info - .iter_explicit_components() - .filter(|component_id| archetype.contains(*component_id)) - }; - if archetype.has_replace_observer() { - deferred_world.trigger_observers( - ON_REPLACE, - entity, - bundle_components_in_archetype(), - caller, - ); + /// Returns the [`Tick`] at which this entity has last been spawned. + pub fn spawned_at(&self) -> Tick { + self.assert_not_despawned(); + + // SAFETY: entity being alive was asserted + unsafe { + self.world() + .entities() + .entity_get_spawned_or_despawned_unchecked(self.entity) + .1 + } } - deferred_world.trigger_on_replace( - archetype, - entity, - bundle_components_in_archetype(), - caller, - RelationshipHookMode::Run, - ); - if archetype.has_remove_observer() { - deferred_world.trigger_observers( - ON_REMOVE, - entity, - bundle_components_in_archetype(), - caller, - ); + + /// Reborrows this entity in a temporary scope. + /// This is useful for executing a function that requires a `EntityWorldMut` + /// but you do not want to move out the entity ownership. + pub fn reborrow_scope(&mut self, f: impl FnOnce(EntityWorldMut) -> U) -> U { + let Self { + entity, location, .. + } = *self; + self.world_scope(move |world| { + f(EntityWorldMut { + world, + entity, + location, + }) + }) } - deferred_world.trigger_on_remove(archetype, entity, bundle_components_in_archetype(), caller); } /// A view into a single entity and component in a world, which may either be vacant or occupied. @@ -3127,14 +3205,6 @@ impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> { /// /// let filtered_entity: FilteredEntityRef = query.single(&mut world).unwrap(); /// let component: &A = filtered_entity.get().unwrap(); -/// -/// // Here `FilteredEntityRef` is nested in a tuple, so it does not have access to `&A`. -/// let mut query = QueryBuilder::<(Entity, FilteredEntityRef)>::new(&mut world) -/// .data::<&A>() -/// .build(); -/// -/// let (_, filtered_entity) = query.single(&mut world).unwrap(); -/// assert!(filtered_entity.get::
().is_none()); /// ``` #[derive(Clone)] pub struct FilteredEntityRef<'w> { @@ -3289,6 +3359,11 @@ impl<'w> FilteredEntityRef<'w> { pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } + + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.entity.spawned_at() + } } impl<'w> From> for FilteredEntityRef<'w> { @@ -3427,14 +3502,14 @@ impl Hash for FilteredEntityRef<'_> { } } -impl EntityBorrow for FilteredEntityRef<'_> { +impl ContainsEntity for FilteredEntityRef<'_> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for FilteredEntityRef<'_> {} +unsafe impl EntityEquivalent for FilteredEntityRef<'_> {} /// Provides mutable access to a single entity and some of its components defined by the contained [`Access`]. /// @@ -3458,14 +3533,6 @@ unsafe impl TrustedEntityBorrow for FilteredEntityRef<'_> {} /// /// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world).unwrap(); /// let component: Mut = filtered_entity.get_mut().unwrap(); -/// -/// // Here `FilteredEntityMut` is nested in a tuple, so it does not have access to `&mut A`. -/// let mut query = QueryBuilder::<(Entity, FilteredEntityMut)>::new(&mut world) -/// .data::<&mut A>() -/// .build(); -/// -/// let (_, mut filtered_entity) = query.single_mut(&mut world).unwrap(); -/// assert!(filtered_entity.get_mut::().is_none()); /// ``` pub struct FilteredEntityMut<'w> { entity: UnsafeEntityCell<'w>, @@ -3670,6 +3737,11 @@ impl<'w> FilteredEntityMut<'w> { pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } + + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.entity.spawned_at() + } } impl<'a> From> for FilteredEntityMut<'a> { @@ -3770,14 +3842,14 @@ impl Hash for FilteredEntityMut<'_> { } } -impl EntityBorrow for FilteredEntityMut<'_> { +impl ContainsEntity for FilteredEntityMut<'_> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for FilteredEntityMut<'_> {} +unsafe impl EntityEquivalent for FilteredEntityMut<'_> {} /// Error type returned by [`TryFrom`] conversions from filtered entity types /// ([`FilteredEntityRef`]/[`FilteredEntityMut`]) to full-access entity types @@ -3868,6 +3940,11 @@ where self.entity.spawned_by() } + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.entity.spawned_at() + } + /// Gets the component of the given [`ComponentId`] from the entity. /// /// **You should prefer to use the typed API [`Self::get`] where possible and only @@ -4003,14 +4080,14 @@ impl Hash for EntityRefExcept<'_, B> { } } -impl EntityBorrow for EntityRefExcept<'_, B> { +impl ContainsEntity for EntityRefExcept<'_, B> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for EntityRefExcept<'_, B> {} +unsafe impl EntityEquivalent for EntityRefExcept<'_, B> {} /// Provides mutable access to all components of an entity, with the exception /// of an explicit set. @@ -4112,6 +4189,11 @@ where self.entity.spawned_by() } + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(&self) -> Tick { + self.entity.spawned_at() + } + /// Returns `true` if the current entity has a component of type `T`. /// Otherwise, this returns `false`. /// @@ -4213,14 +4295,14 @@ impl Hash for EntityMutExcept<'_, B> { } } -impl EntityBorrow for EntityMutExcept<'_, B> { +impl ContainsEntity for EntityMutExcept<'_, B> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl TrustedEntityBorrow for EntityMutExcept<'_, B> {} +unsafe impl EntityEquivalent for EntityMutExcept<'_, B> {} fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool where @@ -4240,7 +4322,7 @@ where /// # Safety /// /// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the -/// [`BundleInfo`] used to construct [`BundleInserter`] +/// [`BundleInfo`](crate::bundle::BundleInfo) used to construct [`BundleInserter`] /// - [`Entity`] must correspond to [`EntityLocation`] unsafe fn insert_dynamic_bundle< 'a, @@ -4288,50 +4370,6 @@ unsafe fn insert_dynamic_bundle< } } -/// Moves component data out of storage. -/// -/// This function leaves the underlying memory unchanged, but the component behind -/// returned pointer is semantically owned by the caller and will not be dropped in its original location. -/// Caller is responsible to drop component data behind returned pointer. -/// -/// # Safety -/// - `location.table_row` must be in bounds of column of component id `component_id` -/// - `component_id` must be valid -/// - `components` must come from the same world as `self` -/// - The relevant table row **must be removed** by the caller once all components are taken, without dropping the value -/// -/// # Panics -/// Panics if the entity did not have the component. -#[inline] -pub(crate) unsafe fn take_component<'a>( - storages: &'a mut Storages, - components: &Components, - removed_components: &mut RemovedComponentEvents, - component_id: ComponentId, - entity: Entity, - location: EntityLocation, -) -> OwningPtr<'a> { - // SAFETY: caller promises component_id to be valid - let component_info = unsafe { components.get_info_unchecked(component_id) }; - removed_components.send(component_id, entity); - match component_info.storage_type() { - StorageType::Table => { - let table = &mut storages.tables[location.table_id]; - // SAFETY: - // - archetypes only store valid table_rows - // - index is in bounds as promised by caller - // - promote is safe because the caller promises to remove the table row without dropping it immediately afterwards - unsafe { table.take_component(component_id, location.table_row) } - } - StorageType::SparseSet => storages - .sparse_sets - .get_mut(component_id) - .unwrap() - .remove_and_forget(entity) - .unwrap(), - } -} - /// Types that can be used to fetch components from an entity dynamically by /// [`ComponentId`]s. /// @@ -4396,6 +4434,26 @@ pub unsafe trait DynamicComponentFetch { self, cell: UnsafeEntityCell<'_>, ) -> Result, EntityComponentError>; + + /// Returns untyped mutable reference(s) to the component(s) with the + /// given [`ComponentId`]s, as determined by `self`. + /// Assumes all [`ComponentId`]s refer to mutable components. + /// + /// # Safety + /// + /// It is the caller's responsibility to ensure that: + /// - The given [`UnsafeEntityCell`] has mutable access to the fetched components. + /// - No other references to the fetched components exist at the same time. + /// - The requested components are all mutable. + /// + /// # Errors + /// + /// - Returns [`EntityComponentError::MissingComponent`] if a component is missing from the entity. + /// - Returns [`EntityComponentError::AliasedMutability`] if a component is requested multiple times. + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError>; } // SAFETY: @@ -4421,6 +4479,15 @@ unsafe impl DynamicComponentFetch for ComponentId { unsafe { cell.get_mut_by_id(self) } .map_err(|_| EntityComponentError::MissingComponent(self)) } + + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError> { + // SAFETY: caller ensures that the cell has mutable access to the component. + unsafe { cell.get_mut_assume_mutable_by_id(self) } + .map_err(|_| EntityComponentError::MissingComponent(self)) + } } // SAFETY: @@ -4443,6 +4510,13 @@ unsafe impl DynamicComponentFetch for [ComponentId; N] { ) -> Result, EntityComponentError> { <&Self>::fetch_mut(&self, cell) } + + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError> { + <&Self>::fetch_mut_assume_mutable(&self, cell) + } } // SAFETY: @@ -4497,6 +4571,34 @@ unsafe impl DynamicComponentFetch for &'_ [ComponentId; N] { Ok(ptrs) } + + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError> { + // Check for duplicate component IDs. + for i in 0..self.len() { + for j in 0..i { + if self[i] == self[j] { + return Err(EntityComponentError::AliasedMutability(self[i])); + } + } + } + + let mut ptrs = [const { MaybeUninit::uninit() }; N]; + for (ptr, &id) in core::iter::zip(&mut ptrs, self) { + *ptr = MaybeUninit::new( + // SAFETY: caller ensures that the cell has mutable access to the component. + unsafe { cell.get_mut_assume_mutable_by_id(id) } + .map_err(|_| EntityComponentError::MissingComponent(id))?, + ); + } + + // SAFETY: Each ptr was initialized in the loop above. + let ptrs = ptrs.map(|ptr| unsafe { MaybeUninit::assume_init(ptr) }); + + Ok(ptrs) + } } // SAFETY: @@ -4543,6 +4645,30 @@ unsafe impl DynamicComponentFetch for &'_ [ComponentId] { } Ok(ptrs) } + + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError> { + // Check for duplicate component IDs. + for i in 0..self.len() { + for j in 0..i { + if self[i] == self[j] { + return Err(EntityComponentError::AliasedMutability(self[i])); + } + } + } + + let mut ptrs = Vec::with_capacity(self.len()); + for &id in self { + ptrs.push( + // SAFETY: caller ensures that the cell has mutable access to the component. + unsafe { cell.get_mut_assume_mutable_by_id(id) } + .map_err(|_| EntityComponentError::MissingComponent(id))?, + ); + } + Ok(ptrs) + } } // SAFETY: @@ -4582,6 +4708,22 @@ unsafe impl DynamicComponentFetch for &'_ HashSet { } Ok(ptrs) } + + unsafe fn fetch_mut_assume_mutable( + self, + cell: UnsafeEntityCell<'_>, + ) -> Result, EntityComponentError> { + let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); + for &id in self { + ptrs.insert( + id, + // SAFETY: caller ensures that the cell has mutable access to the component. + unsafe { cell.get_mut_assume_mutable_by_id(id) } + .map_err(|_| EntityComponentError::MissingComponent(id))?, + ); + } + Ok(ptrs) + } } #[cfg(test)] @@ -4591,7 +4733,7 @@ mod tests { use core::panic::AssertUnwindSafe; use std::sync::OnceLock; - use crate::component::HookContext; + use crate::component::{HookContext, Tick}; use crate::{ change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, @@ -5836,7 +5978,7 @@ mod tests { struct A; #[derive(Component, Clone, PartialEq, Debug, Default)] - #[require(C(|| C(3)))] + #[require(C(3))] struct B; #[derive(Component, Clone, PartialEq, Debug, Default)] @@ -5879,22 +6021,27 @@ mod tests { #[component(on_remove = get_tracked)] struct C; - static TRACKED: OnceLock = OnceLock::new(); + static TRACKED: OnceLock<(MaybeLocation, Tick)> = OnceLock::new(); fn get_tracked(world: DeferredWorld, HookContext { entity, .. }: HookContext) { TRACKED.get_or_init(|| { - world + let by = world .entities .entity_get_spawned_or_despawned_by(entity) - .map(|l| l.unwrap()) + .map(|l| l.unwrap()); + let at = world + .entities + .entity_get_spawned_or_despawned_at(entity) + .unwrap(); + (by, at) }); } #[track_caller] - fn caller_spawn(world: &mut World) -> (Entity, MaybeLocation) { + fn caller_spawn(world: &mut World) -> (Entity, MaybeLocation, Tick) { let caller = MaybeLocation::caller(); - (world.spawn(C).id(), caller) + (world.spawn(C).id(), caller, world.change_tick()) } - let (entity, spawner) = caller_spawn(&mut world); + let (entity, spawner, spawn_tick) = caller_spawn(&mut world); assert_eq!( spawner, @@ -5905,13 +6052,13 @@ mod tests { ); #[track_caller] - fn caller_despawn(world: &mut World, entity: Entity) -> MaybeLocation { + fn caller_despawn(world: &mut World, entity: Entity) -> (MaybeLocation, Tick) { world.despawn(entity); - MaybeLocation::caller() + (MaybeLocation::caller(), world.change_tick()) } - let despawner = caller_despawn(&mut world, entity); + let (despawner, despawn_tick) = caller_despawn(&mut world, entity); - assert_eq!(spawner, *TRACKED.get().unwrap()); + assert_eq!((spawner, spawn_tick), *TRACKED.get().unwrap()); assert_eq!( despawner, world @@ -5919,6 +6066,13 @@ mod tests { .entity_get_spawned_or_despawned_by(entity) .map(|l| l.unwrap()) ); + assert_eq!( + despawn_tick, + world + .entities() + .entity_get_spawned_or_despawned_at(entity) + .unwrap() + ); } #[test] diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index 221ddd8210..6b1c803e75 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -4,7 +4,7 @@ use crate::{ system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, world::{FromWorld, World}, }; -use bevy_platform_support::sync::atomic::{AtomicUsize, Ordering}; +use bevy_platform::sync::atomic::{AtomicUsize, Ordering}; use super::unsafe_world_cell::UnsafeWorldCell; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f9f8828015..28a648c318 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -14,6 +14,7 @@ pub mod unsafe_world_cell; #[cfg(feature = "bevy_reflect")] pub mod reflect; +use crate::error::{DefaultErrorHandler, ErrorHandler}; pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, @@ -30,12 +31,8 @@ pub use filtered_resource::*; pub use identifier::WorldId; pub use spawn_batch::*; -#[expect( - deprecated, - reason = "We need to support `AllocAtWithoutReplacement` for now." -)] use crate::{ - archetype::{ArchetypeId, ArchetypeRow, Archetypes}, + archetype::{ArchetypeId, Archetypes}, bundle::{ Bundle, BundleEffect, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode, NoBundleEffect, @@ -46,9 +43,7 @@ use crate::{ ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, Tick, }, - entity::{ - AllocAtWithoutReplacement, Entities, Entity, EntityDoesNotExistError, EntityLocation, - }, + entity::{Entities, Entity, EntityDoesNotExistError, EntityLocation}, entity_disabling::DefaultQueryFilters, event::{Event, EventId, Events, SendBatchIds}, observer::Observers, @@ -67,7 +62,7 @@ use crate::{ }, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform_support::sync::atomic::{AtomicU32, Ordering}; +use bevy_platform::sync::atomic::{AtomicU32, Ordering}; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use core::{any::TypeId, fmt}; use log::warn; @@ -304,7 +299,7 @@ impl World { &mut self, id: ComponentId, ) -> Option<&mut ComponentHooks> { - assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(id)), "Components hooks cannot be modified if the component already exists in an archetype, use register_component if the component with id {:?} may already be in use", id); + assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(id)), "Components hooks cannot be modified if the component already exists in an archetype, use register_component if the component with id {id:?} may already be in use"); self.components.get_hooks_mut(id) } @@ -689,10 +684,10 @@ impl World { /// } /// ``` /// - /// ## [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) + /// ## [`EntityHashSet`](crate::entity::EntityHashMap) /// /// ``` - /// # use bevy_ecs::{prelude::*, entity::hash_set::EntityHashSet}; + /// # use bevy_ecs::{prelude::*, entity::EntityHashSet}; /// #[derive(Component)] /// struct Position { /// x: f32, @@ -710,7 +705,7 @@ impl World { /// } /// ``` /// - /// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet + /// [`EntityHashSet`]: crate::entity::EntityHashSet #[inline] #[track_caller] pub fn entity(&self, entities: F) -> F::Ref<'_> { @@ -741,8 +736,8 @@ impl World { /// such as adding or removing components, or despawning the entity. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityMut`]s. - /// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an - /// [`EntityHashMap`](crate::entity::hash_map::EntityHashMap). + /// - Pass a reference to a [`EntityHashSet`](crate::entity::EntityHashMap) to receive an + /// [`EntityHashMap`](crate::entity::EntityHashMap). /// /// In order to perform structural changes on the returned entity reference, /// such as adding or removing components, or despawning the entity, only a @@ -823,10 +818,10 @@ impl World { /// } /// ``` /// - /// ## [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) + /// ## [`EntityHashSet`](crate::entity::EntityHashMap) /// /// ``` - /// # use bevy_ecs::{prelude::*, entity::hash_set::EntityHashSet}; + /// # use bevy_ecs::{prelude::*, entity::EntityHashSet}; /// #[derive(Component)] /// struct Position { /// x: f32, @@ -846,7 +841,7 @@ impl World { /// } /// ``` /// - /// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet + /// [`EntityHashSet`]: crate::entity::EntityHashSet #[inline] #[track_caller] pub fn entity_mut(&mut self, entities: F) -> F::Mut<'_> { @@ -893,8 +888,8 @@ impl World { /// - Pass an [`Entity`] to receive a single [`EntityRef`]. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityRef`]s. - /// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an - /// [`EntityHashMap`](crate::entity::hash_map::EntityHashMap). + /// - Pass a reference to a [`EntityHashSet`](crate::entity::EntityHashMap) to receive an + /// [`EntityHashMap`](crate::entity::EntityHashMap). /// /// # Errors /// @@ -905,7 +900,7 @@ impl World { /// /// For examples, see [`World::entity`]. /// - /// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet + /// [`EntityHashSet`]: crate::entity::EntityHashSet #[inline] pub fn get_entity( &self, @@ -927,8 +922,8 @@ impl World { /// such as adding or removing components, or despawning the entity. /// - Pass a slice of [`Entity`]s to receive a [`Vec`]. /// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityMut`]s. - /// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an - /// [`EntityHashMap`](crate::entity::hash_map::EntityHashMap). + /// - Pass a reference to a [`EntityHashSet`](crate::entity::EntityHashMap) to receive an + /// [`EntityHashMap`](crate::entity::EntityHashMap). /// /// In order to perform structural changes on the returned entity reference, /// such as adding or removing components, or despawning the entity, only a @@ -946,7 +941,7 @@ impl World { /// /// For examples, see [`World::entity_mut`]. /// - /// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet + /// [`EntityHashSet`]: crate::entity::EntityHashSet #[inline] pub fn get_entity_mut( &mut self, @@ -965,23 +960,15 @@ impl World { pub fn iter_entities(&self) -> impl Iterator> + '_ { self.archetypes.iter().flat_map(|archetype| { archetype - .entities() - .iter() - .enumerate() - .map(|(archetype_row, archetype_entity)| { - let entity = archetype_entity.id(); - let location = EntityLocation { - archetype_id: archetype.id(), - archetype_row: ArchetypeRow::new(archetype_row), - table_id: archetype.table_id(), - table_row: archetype_entity.table_row(), - }; - + .entities_with_location() + .map(|(entity, location)| { // SAFETY: entity exists and location accurately specifies the archetype where the entity is stored. let cell = UnsafeEntityCell::new( self.as_unsafe_world_cell_readonly(), entity, location, + self.last_change_tick, + self.read_change_tick(), ); // SAFETY: `&self` gives read access to the entire world. unsafe { EntityRef::new(cell) } @@ -991,23 +978,21 @@ impl World { /// Returns a mutable iterator over all entities in the `World`. pub fn iter_entities_mut(&mut self) -> impl Iterator> + '_ { + let last_change_tick = self.last_change_tick; + let change_tick = self.change_tick(); let world_cell = self.as_unsafe_world_cell(); world_cell.archetypes().iter().flat_map(move |archetype| { archetype - .entities() - .iter() - .enumerate() - .map(move |(archetype_row, archetype_entity)| { - let entity = archetype_entity.id(); - let location = EntityLocation { - archetype_id: archetype.id(), - archetype_row: ArchetypeRow::new(archetype_row), - table_id: archetype.table_id(), - table_row: archetype_entity.table_row(), - }; - + .entities_with_location() + .map(move |(entity, location)| { // SAFETY: entity exists and location accurately specifies the archetype where the entity is stored. - let cell = UnsafeEntityCell::new(world_cell, entity, location); + let cell = UnsafeEntityCell::new( + world_cell, + entity, + location, + last_change_tick, + change_tick, + ); // SAFETY: We have exclusive access to the entire world. We only create one borrow for each entity, // so none will conflict with one another. unsafe { EntityMut::new(cell) } @@ -1174,16 +1159,13 @@ impl World { // SAFETY: command_queue is not referenced anywhere else if !unsafe { self.command_queue.is_empty() } { - self.flush_commands(); + self.flush(); entity_location = self .entities() .get(entity) .unwrap_or(EntityLocation::INVALID); } - self.entities - .set_spawned_or_despawned_by(entity.index(), caller); - // SAFETY: entity and location are valid, as they were just created above let mut entity = unsafe { EntityWorldMut::new(self, entity, entity_location) }; after_effect.apply(&mut entity); @@ -1203,10 +1185,10 @@ impl World { // SAFETY: no components are allocated by archetype.allocate() because the archetype is // empty let location = unsafe { archetype.allocate(entity, table_row) }; + let change_tick = self.change_tick(); self.entities.set(entity.index(), location); - self.entities - .set_spawned_or_despawned_by(entity.index(), caller); + .mark_spawn_despawn(entity.index(), caller, change_tick); EntityWorldMut::new(self, entity, location) } @@ -1332,6 +1314,35 @@ impl World { Ok(result) } + /// Temporarily removes a [`Component`] identified by the provided + /// [`ComponentId`] from the provided [`Entity`] and runs the provided + /// closure on it, returning the result if the component was available. + /// This will trigger the `OnRemove` and `OnReplace` component hooks without + /// causing an archetype move. + /// + /// This is most useful with immutable components, where removal and reinsertion + /// is the only way to modify a value. + /// + /// If you do not need to ensure the above hooks are triggered, and your component + /// is mutable, prefer using [`get_mut_by_id`](World::get_mut_by_id). + /// + /// You should prefer the typed [`modify_component`](World::modify_component) + /// whenever possible. + #[inline] + pub fn modify_component_by_id( + &mut self, + entity: Entity, + component_id: ComponentId, + f: impl for<'a> FnOnce(MutUntyped<'a>) -> R, + ) -> Result, EntityMutableFetchError> { + let mut world = DeferredWorld::from(&mut *self); + + let result = world.modify_component_by_id(entity, component_id, f)?; + + self.flush(); + Ok(result) + } + /// Despawns the given [`Entity`], if it exists. This will also remove all of the entity's /// [`Components`](Component). /// @@ -2193,174 +2204,6 @@ impl World { unsafe { self.as_unsafe_world_cell().get_non_send_resource_mut() } } - /// For a given batch of ([`Entity`], [`Bundle`]) pairs, either spawns each [`Entity`] with the given - /// bundle (if the entity does not exist), or inserts the [`Bundle`] (if the entity already exists). - /// This is faster than doing equivalent operations one-by-one. - /// Returns `Ok` if all entities were successfully inserted into or spawned. Otherwise it returns an `Err` - /// with a list of entities that could not be spawned or inserted into. A "spawn or insert" operation can - /// only fail if an [`Entity`] is passed in with an "invalid generation" that conflicts with an existing [`Entity`]. - /// - /// # Note - /// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`World::spawn_batch`]. - /// This method should generally only be used for sharing entities across apps, and only when they have a scheme - /// worked out to share an ID space (which doesn't happen by default). - /// - /// ``` - /// use bevy_ecs::{entity::Entity, world::World, component::Component}; - /// #[derive(Component)] - /// struct A(&'static str); - /// #[derive(Component, PartialEq, Debug)] - /// struct B(f32); - /// - /// let mut world = World::new(); - /// let e0 = world.spawn_empty().id(); - /// let e1 = world.spawn_empty().id(); - /// world.insert_or_spawn_batch(vec![ - /// (e0, (A("a"), B(0.0))), // the first entity - /// (e1, (A("b"), B(1.0))), // the second entity - /// ]); - /// - /// assert_eq!(world.get::(e0), Some(&B(0.0))); - /// ``` - #[track_caller] - #[deprecated( - note = "This can cause extreme performance problems when used with lots of arbitrary free entities. See #18054 on GitHub." - )] - pub fn insert_or_spawn_batch(&mut self, iter: I) -> Result<(), Vec> - where - I: IntoIterator, - I::IntoIter: Iterator, - B: Bundle, - { - #[expect( - deprecated, - reason = "This needs to be supported for now, and the outer function is deprecated too." - )] - self.insert_or_spawn_batch_with_caller(iter, MaybeLocation::caller()) - } - - /// Split into a new function so we can pass the calling location into the function when using - /// as a command. - #[inline] - #[deprecated( - note = "This can cause extreme performance problems when used with lots of arbitrary free entities. See #18054 on GitHub." - )] - pub(crate) fn insert_or_spawn_batch_with_caller( - &mut self, - iter: I, - caller: MaybeLocation, - ) -> Result<(), Vec> - where - I: IntoIterator, - I::IntoIter: Iterator, - B: Bundle, - { - self.flush(); - let change_tick = self.change_tick(); - - // SAFETY: These come from the same world. `Self.components_registrator` can't be used since we borrow other fields too. - let mut registrator = - unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; - let bundle_id = self - .bundles - .register_info::(&mut registrator, &mut self.storages); - enum SpawnOrInsert<'w> { - Spawn(BundleSpawner<'w>), - Insert(BundleInserter<'w>, ArchetypeId), - } - - impl<'w> SpawnOrInsert<'w> { - fn entities(&mut self) -> &mut Entities { - match self { - SpawnOrInsert::Spawn(spawner) => spawner.entities(), - SpawnOrInsert::Insert(inserter, _) => inserter.entities(), - } - } - } - // SAFETY: we initialized this bundle_id in `init_info` - let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { - BundleSpawner::new_with_id(self, bundle_id, change_tick) - }); - - let mut invalid_entities = Vec::new(); - for (entity, bundle) in iter { - #[expect( - deprecated, - reason = "This needs to be supported for now, and the outer function is deprecated too." - )] - match spawn_or_insert - .entities() - .alloc_at_without_replacement(entity) - { - AllocAtWithoutReplacement::Exists(location) => { - match spawn_or_insert { - SpawnOrInsert::Insert(ref mut inserter, archetype) - if location.archetype_id == archetype => - { - // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { - inserter.insert( - entity, - location, - bundle, - InsertMode::Replace, - caller, - RelationshipHookMode::Run, - ) - }; - } - _ => { - // SAFETY: we initialized this bundle_id in `init_info` - let mut inserter = unsafe { - BundleInserter::new_with_id( - self, - location.archetype_id, - bundle_id, - change_tick, - ) - }; - // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { - inserter.insert( - entity, - location, - bundle, - InsertMode::Replace, - caller, - RelationshipHookMode::Run, - ) - }; - spawn_or_insert = - SpawnOrInsert::Insert(inserter, location.archetype_id); - } - }; - } - AllocAtWithoutReplacement::DidNotExist => { - if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert { - // SAFETY: `entity` is allocated (but non existent), bundle matches inserter - unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; - } else { - // SAFETY: we initialized this bundle_id in `init_info` - let mut spawner = - unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; - // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; - spawn_or_insert = SpawnOrInsert::Spawn(spawner); - } - } - AllocAtWithoutReplacement::ExistsWithWrongGeneration => { - invalid_entities.push(entity); - } - } - } - - if invalid_entities.is_empty() { - Ok(()) - } else { - Err(invalid_entities) - } - } - /// For a given batch of ([`Entity`], [`Bundle`]) pairs, /// adds the `Bundle` of components to each `Entity`. /// This is faster than doing equivalent operations one-by-one. @@ -2847,12 +2690,10 @@ impl World { &mut self, component_id: ComponentId, ) -> &mut ResourceData { - let archetypes = &mut self.archetypes; + self.flush_components(); self.storages .resources - .initialize_with(component_id, &self.components, || { - archetypes.new_archetype_component_id() - }) + .initialize_with(component_id, &self.components) } /// # Panics @@ -2862,28 +2703,33 @@ impl World { &mut self, component_id: ComponentId, ) -> &mut ResourceData { - let archetypes = &mut self.archetypes; + self.flush_components(); self.storages .non_send_resources - .initialize_with(component_id, &self.components, || { - archetypes.new_archetype_component_id() - }) + .initialize_with(component_id, &self.components) } /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [`Component`]. + #[track_caller] pub(crate) fn flush_entities(&mut self) { + let by = MaybeLocation::caller(); + let at = self.change_tick(); let empty_archetype = self.archetypes.empty_mut(); let table = &mut self.storages.tables[empty_archetype.table_id()]; // PERF: consider pre-allocating space for flushed entities // SAFETY: entity is set to a valid location unsafe { - self.entities.flush(|entity, location| { - // SAFETY: no components are allocated by archetype.allocate() because the archetype - // is empty - *location = empty_archetype.allocate(entity, table.allocate(entity)); - }); + self.entities.flush( + |entity, location| { + // SAFETY: no components are allocated by archetype.allocate() because the archetype + // is empty + *location = empty_archetype.allocate(entity, table.allocate(entity)); + }, + by, + at, + ); } } @@ -2918,6 +2764,7 @@ impl World { /// /// Queued entities will be spawned, and then commands will be applied. #[inline] + #[track_caller] pub fn flush(&mut self) { self.flush_entities(); self.flush_components(); @@ -3112,6 +2959,7 @@ impl World { sparse_sets.check_change_ticks(change_tick); resources.check_change_ticks(change_tick); non_send_resources.check_change_ticks(change_tick); + self.entities.check_change_ticks(change_tick); if let Some(mut schedules) = self.get_resource_mut::() { schedules.check_change_ticks(change_tick); @@ -3182,6 +3030,16 @@ impl World { // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } + + /// Convenience method for accessing the world's default error handler, + /// which can be overwritten with [`DefaultErrorHandler`]. + #[inline] + pub fn default_error_handler(&self) -> ErrorHandler { + self.get_resource::() + .copied() + .unwrap_or_default() + .0 + } } impl World { @@ -3527,11 +3385,18 @@ impl World { // Schedule-related methods impl World { - /// Adds the specified [`Schedule`] to the world. The schedule can later be run + /// Adds the specified [`Schedule`] to the world. + /// If a schedule already exists with the same [label](Schedule::label), it will be replaced. + /// + /// The schedule can later be run /// by calling [`.run_schedule(label)`](Self::run_schedule) or by directly /// accessing the [`Schedules`] resource. /// /// The `Schedules` resource will be initialized if it does not already exist. + /// + /// An alternative to this is to call [`Schedules::add_systems()`] with some + /// [`ScheduleLabel`] and let the schedule for that label be created if it + /// does not already exist. pub fn add_schedule(&mut self, schedule: Schedule) { let mut schedules = self.get_resource_or_init::(); schedules.insert(schedule); @@ -3637,6 +3502,7 @@ impl World { /// and system state is cached. /// /// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead. + /// This avoids the need to create a unique [`ScheduleLabel`]. /// /// # Panics /// @@ -3734,7 +3600,7 @@ mod tests { use crate::{ change_detection::{DetectChangesMut, MaybeLocation}, component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, - entity::hash_set::EntityHashSet, + entity::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled}, ptr::OwningPtr, resource::Resource, @@ -3748,7 +3614,7 @@ mod tests { vec::Vec, }; use bevy_ecs_macros::Component; - use bevy_platform_support::collections::{HashMap, HashSet}; + use bevy_platform::collections::{HashMap, HashSet}; use core::{ any::TypeId, panic, @@ -4405,22 +4271,38 @@ mod tests { world.entities.entity_get_spawned_or_despawned_by(entity), MaybeLocation::new(Some(Location::caller())) ); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_at(entity), + Some(world.change_tick()) + ); world.despawn(entity); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), MaybeLocation::new(Some(Location::caller())) ); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_at(entity), + Some(world.change_tick()) + ); let new = world.spawn_empty().id(); assert_eq!(entity.index(), new.index()); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), MaybeLocation::new(None) ); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_at(entity), + None + ); world.despawn(new); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), MaybeLocation::new(None) ); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_at(entity), + None + ); } #[test] diff --git a/crates/bevy_ecs/src/world/reflect.rs b/crates/bevy_ecs/src/world/reflect.rs index 4337416aa2..fdd8b28142 100644 --- a/crates/bevy_ecs/src/world/reflect.rs +++ b/crates/bevy_ecs/src/world/reflect.rs @@ -80,7 +80,7 @@ impl World { let component_name = self .components() .get_name(component_id) - .map(ToString::to_string); + .map(|name| name.to_string()); return Err(GetComponentReflectError::EntityDoesNotHaveComponent { entity, @@ -169,7 +169,7 @@ impl World { let component_name = self .components() .get_name(component_id) - .map(ToString::to_string); + .map(|name| name.to_string()); let Some(comp_mut_untyped) = self.get_mut_by_id(entity, component_id) else { return Err(GetComponentReflectError::EntityDoesNotHaveComponent { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index f094906b12..3f8298cd29 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -6,7 +6,8 @@ use crate::{ bundle::Bundles, change_detection::{MaybeLocation, MutUntyped, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, - entity::{Entities, Entity, EntityBorrow, EntityDoesNotExistError, EntityLocation}, + entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation}, + error::{DefaultErrorHandler, ErrorHandler}, observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReadOnlyQueryData}, @@ -15,7 +16,7 @@ use crate::{ storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; -use bevy_platform_support::sync::atomic::Ordering; +use bevy_platform::sync::atomic::Ordering; use bevy_ptr::{Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; use thiserror::Error; @@ -198,13 +199,13 @@ impl<'w> UnsafeWorldCell<'w> { /// /// # Safety /// - must have permission to access the whole world immutably - /// - there must be no live exclusive borrows on world data + /// - there must be no live exclusive borrows of world data /// - there must be no live exclusive borrow of world #[inline] pub unsafe fn world(self) -> &'w World { // SAFETY: // - caller ensures there is no `&mut World` this makes it okay to make a `&World` - // - caller ensures there is no mutable borrows of world data, this means the caller cannot + // - caller ensures there are no mutable borrows of world data, this means the caller cannot // misuse the returned `&World` unsafe { self.unsafe_world() } } @@ -233,7 +234,7 @@ impl<'w> UnsafeWorldCell<'w> { /// /// # Safety /// - must not be used in a way that would conflict with any - /// live exclusive borrows on world data + /// live exclusive borrows of world data #[inline] unsafe fn unsafe_world(self) -> &'w World { // SAFETY: @@ -365,13 +366,37 @@ impl<'w> UnsafeWorldCell<'w> { .entities() .get(entity) .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; - Ok(UnsafeEntityCell::new(self, entity, location)) + Ok(UnsafeEntityCell::new( + self, + entity, + location, + self.last_change_tick(), + self.change_tick(), + )) + } + + /// Retrieves an [`UnsafeEntityCell`] that exposes read and write operations for the given `entity`. + /// Similar to the [`UnsafeWorldCell`], you are in charge of making sure that no aliasing rules are violated. + #[inline] + pub fn get_entity_with_ticks( + self, + entity: Entity, + last_run: Tick, + this_run: Tick, + ) -> Result, EntityDoesNotExistError> { + let location = self + .entities() + .get(entity) + .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + Ok(UnsafeEntityCell::new( + self, entity, location, last_run, this_run, + )) } /// Gets a reference to the resource of the given type if it exists /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource /// - no mutable reference to the resource exists at the same time #[inline] @@ -389,7 +414,7 @@ impl<'w> UnsafeWorldCell<'w> { /// Gets a reference including change detection to the resource of the given type if it exists. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource /// - no mutable reference to the resource exists at the same time #[inline] @@ -397,7 +422,7 @@ impl<'w> UnsafeWorldCell<'w> { let component_id = self.components().get_resource_id(TypeId::of::())?; // SAFETY: caller ensures `self` has permission to access the resource - // caller also ensure that no mutable reference to the resource exists + // caller also ensures that no mutable reference to the resource exists let (ptr, ticks, caller) = unsafe { self.get_resource_with_ticks(component_id)? }; // SAFETY: `component_id` was obtained from the type ID of `R` @@ -425,7 +450,7 @@ impl<'w> UnsafeWorldCell<'w> { /// use this in cases where the actual types are not known at compile time.** /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource /// - no mutable reference to the resource exists at the same time #[inline] @@ -441,7 +466,7 @@ impl<'w> UnsafeWorldCell<'w> { /// Gets a reference to the non-send resource of the given type if it exists /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource /// - no mutable reference to the resource exists at the same time #[inline] @@ -467,7 +492,7 @@ impl<'w> UnsafeWorldCell<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource /// - no mutable reference to the resource exists at the same time #[inline] @@ -483,7 +508,7 @@ impl<'w> UnsafeWorldCell<'w> { /// Gets a mutable reference to the resource of the given type if it exists /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no other references to the resource exist at the same time #[inline] @@ -508,7 +533,7 @@ impl<'w> UnsafeWorldCell<'w> { /// use this in cases where the actual types are not known at compile time.** /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no other references to the resource exist at the same time #[inline] @@ -547,7 +572,7 @@ impl<'w> UnsafeWorldCell<'w> { /// Gets a mutable reference to the non-send resource of the given type if it exists /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no other references to the resource exist at the same time #[inline] @@ -575,7 +600,7 @@ impl<'w> UnsafeWorldCell<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no other references to the resource exist at the same time #[inline] @@ -609,7 +634,7 @@ impl<'w> UnsafeWorldCell<'w> { // Shorthand helper function for getting the data and change ticks for a resource. /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no mutable references to the resource exist at the same time #[inline] @@ -636,7 +661,7 @@ impl<'w> UnsafeWorldCell<'w> { /// This function will panic if it isn't called from the same thread that the resource was inserted from. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the resource mutably /// - no mutable references to the resource exist at the same time #[inline] @@ -660,7 +685,7 @@ impl<'w> UnsafeWorldCell<'w> { // Returns a mutable reference to the underlying world's [`CommandQueue`]. /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeWorldCell`] has permission to access the queue mutably /// - no mutable references to the queue exist at the same time pub(crate) unsafe fn get_raw_command_queue(self) -> RawCommandQueue { @@ -672,7 +697,7 @@ impl<'w> UnsafeWorldCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that there are no outstanding + /// It is the caller's responsibility to ensure that there are no outstanding /// references to `last_trigger_id`. pub(crate) unsafe fn increment_trigger_id(self) { self.assert_allows_mutable_access(); @@ -681,6 +706,18 @@ impl<'w> UnsafeWorldCell<'w> { (*self.ptr).last_trigger_id = (*self.ptr).last_trigger_id.wrapping_add(1); } } + + /// Convenience method for accessing the world's default error handler, + /// + /// # Safety + /// Must have read access to [`DefaultErrorHandler`]. + #[inline] + pub unsafe fn default_error_handler(&self) -> ErrorHandler { + self.get_resource::() + .copied() + .unwrap_or_default() + .0 + } } impl Debug for UnsafeWorldCell<'_> { @@ -690,12 +727,14 @@ impl Debug for UnsafeWorldCell<'_> { } } -/// A interior-mutable reference to a particular [`Entity`] and all of its components +/// An interior-mutable reference to a particular [`Entity`] and all of its components #[derive(Copy, Clone)] pub struct UnsafeEntityCell<'w> { world: UnsafeWorldCell<'w>, entity: Entity, location: EntityLocation, + last_run: Tick, + this_run: Tick, } impl<'w> UnsafeEntityCell<'w> { @@ -704,11 +743,15 @@ impl<'w> UnsafeEntityCell<'w> { world: UnsafeWorldCell<'w>, entity: Entity, location: EntityLocation, + last_run: Tick, + this_run: Tick, ) -> Self { UnsafeEntityCell { world, entity, location, + last_run, + this_run, } } @@ -778,7 +821,7 @@ impl<'w> UnsafeEntityCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] @@ -802,13 +845,13 @@ impl<'w> UnsafeEntityCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] pub unsafe fn get_ref(self) -> Option> { - let last_change_tick = self.world.last_change_tick(); - let change_tick = self.world.change_tick(); + let last_change_tick = self.last_run; + let change_tick = self.this_run; let component_id = self.world.components().get_id(TypeId::of::())?; // SAFETY: @@ -836,7 +879,7 @@ impl<'w> UnsafeEntityCell<'w> { /// detection in custom runtimes. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] @@ -865,7 +908,7 @@ impl<'w> UnsafeEntityCell<'w> { /// compile time.** /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] @@ -890,7 +933,7 @@ impl<'w> UnsafeEntityCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably /// - no other references to the component exist at the same time #[inline] @@ -902,23 +945,18 @@ impl<'w> UnsafeEntityCell<'w> { } /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably /// - no other references to the component exist at the same time /// - the component `T` is mutable #[inline] pub unsafe fn get_mut_assume_mutable(self) -> Option> { // SAFETY: same safety requirements - unsafe { - self.get_mut_using_ticks_assume_mutable( - self.world.last_change_tick(), - self.world.change_tick(), - ) - } + unsafe { self.get_mut_using_ticks_assume_mutable(self.last_run, self.this_run) } } /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably /// - no other references to the component exist at the same time /// - The component `T` is mutable @@ -957,7 +995,7 @@ impl<'w> UnsafeEntityCell<'w> { /// or `None` if the entity does not have the components required by the query `Q`. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably /// - no mutable references to the queried data exist at the same time pub(crate) unsafe fn get_components(&self) -> Option> { @@ -976,14 +1014,8 @@ impl<'w> UnsafeEntityCell<'w> { }; if Q::matches_component_set(&state, &|id| archetype.contains(id)) { // SAFETY: state was initialized above using the world passed into this function - let mut fetch = unsafe { - Q::init_fetch( - self.world, - &state, - self.world.last_change_tick(), - self.world.change_tick(), - ) - }; + let mut fetch = + unsafe { Q::init_fetch(self.world, &state, self.last_run, self.this_run) }; // SAFETY: Table is guaranteed to exist let table = unsafe { self.world @@ -1012,7 +1044,7 @@ impl<'w> UnsafeEntityCell<'w> { /// which is only valid while the `'w` borrow of the lifetime is active. /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] @@ -1037,7 +1069,7 @@ impl<'w> UnsafeEntityCell<'w> { /// use this in cases where the actual types are not known at compile time.** /// /// # Safety - /// It is the callers responsibility to ensure that + /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably /// - no other references to the component exist at the same time #[inline] @@ -1070,11 +1102,51 @@ impl<'w> UnsafeEntityCell<'w> { .map(|(value, cells, caller)| MutUntyped { // SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime value: value.assert_unique(), - ticks: TicksMut::from_tick_cells( - cells, - self.world.last_change_tick(), - self.world.change_tick(), - ), + ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run), + changed_by: caller.map(|caller| caller.deref_mut()), + }) + .ok_or(GetEntityMutByIdError::ComponentNotFound) + } + } + + /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. + /// Returns `None` if the `entity` does not have a [`Component`] of the given type. + /// This method assumes the [`Component`] is mutable, skipping that check. + /// + /// **You should prefer to use the typed API [`UnsafeEntityCell::get_mut_assume_mutable`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Safety + /// It is the caller's responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the component mutably + /// - no other references to the component exist at the same time + /// - the component `T` is mutable + #[inline] + pub unsafe fn get_mut_assume_mutable_by_id( + self, + component_id: ComponentId, + ) -> Result, GetEntityMutByIdError> { + self.world.assert_allows_mutable_access(); + + let info = self + .world + .components() + .get_info(component_id) + .ok_or(GetEntityMutByIdError::InfoNotFound)?; + + // SAFETY: entity_location is valid, component_id is valid as checked by the line above + unsafe { + get_component_and_ticks( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) + .map(|(value, cells, caller)| MutUntyped { + // SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime + value: value.assert_unique(), + ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run), changed_by: caller.map(|caller| caller.deref_mut()), }) .ok_or(GetEntityMutByIdError::ComponentNotFound) @@ -1088,6 +1160,17 @@ impl<'w> UnsafeEntityCell<'w> { .entity_get_spawned_or_despawned_by(self.entity) .map(|o| o.unwrap()) } + + /// Returns the [`Tick`] at which this entity has been spawned. + pub fn spawned_at(self) -> Tick { + // SAFETY: UnsafeEntityCell is only constructed for living entities and offers no despawn method + unsafe { + self.world() + .entities() + .entity_get_spawned_or_despawned_unchecked(self.entity) + .1 + } + } } /// Error that may be returned when calling [`UnsafeEntityCell::get_mut_by_id`]. @@ -1225,7 +1308,7 @@ unsafe fn get_ticks( } } -impl EntityBorrow for UnsafeEntityCell<'_> { +impl ContainsEntity for UnsafeEntityCell<'_> { fn entity(&self) -> Entity { self.id() } diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index eff9c4bf4f..864df285d9 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -15,7 +15,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_input = { path = "../bevy_input", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index db939cf3f0..66cc0e3328 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -15,16 +15,16 @@ mod gilrs_system; mod rumble; #[cfg(not(target_arch = "wasm32"))] -use bevy_utils::synccell::SyncCell; +use bevy_platform::cell::SyncCell; #[cfg(target_arch = "wasm32")] use core::cell::RefCell; use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate}; -use bevy_ecs::entity::hash_map::EntityHashMap; +use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; -use bevy_input::InputSystem; -use bevy_platform_support::collections::HashMap; +use bevy_input::InputSystems; +use bevy_platform::collections::HashMap; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; @@ -84,7 +84,11 @@ pub struct GilrsPlugin; /// Updates the running gamepad rumble effects. #[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)] -pub struct RumbleSystem; +pub struct RumbleSystems; + +/// Deprecated alias for [`RumbleSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `RumbleSystems`.")] +pub type RumbleSystem = RumbleSystems; impl Plugin for GilrsPlugin { fn build(&self, app: &mut App) { @@ -106,10 +110,29 @@ impl Plugin for GilrsPlugin { app.init_resource::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) - .add_systems(PreUpdate, gilrs_event_system.before(InputSystem)) - .add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystem)); + .add_systems(PreUpdate, gilrs_event_system.before(InputSystems)) + .add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystems)); } Err(err) => error!("Failed to start Gilrs. {}", err), } } } + +#[cfg(test)] +mod tests { + use super::*; + + // Regression test for https://github.com/bevyengine/bevy/issues/17697 + #[test] + fn world_is_truly_send() { + let mut app = App::new(); + app.add_plugins(GilrsPlugin); + let world = core::mem::take(app.world_mut()); + + let handler = std::thread::spawn(move || { + drop(world); + }); + + handler.join().unwrap(); + } +} diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 7e49ec77e9..b03fa69fe3 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -2,9 +2,9 @@ use crate::{Gilrs, GilrsGamepads}; use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::cell::SyncCell; +use bevy_platform::collections::HashMap; use bevy_time::{Real, Time}; -use bevy_utils::synccell::SyncCell; use core::time::Duration; use gilrs::{ ff::{self, BaseEffect, BaseEffectType, Repeat, Replay}, diff --git a/crates/bevy_gizmos/src/aabb.rs b/crates/bevy_gizmos/src/aabb.rs index 16dc7ed773..4ac9e5f2ac 100644 --- a/crates/bevy_gizmos/src/aabb.rs +++ b/crates/bevy_gizmos/src/aabb.rs @@ -14,7 +14,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::primitives::Aabb; use bevy_transform::{ components::{GlobalTransform, Transform}, - TransformSystem, + TransformSystems, }; use crate::{ @@ -39,7 +39,7 @@ impl Plugin for AabbGizmoPlugin { }), ) .after(bevy_render::view::VisibilitySystems::CalculateBounds) - .after(TransformSystem::TransformPropagate), + .after(TransformSystems::Propagate), ); } } diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index e094cb4592..87af7c4925 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -11,7 +11,10 @@ use bevy_color::{Color, LinearRgba}; use bevy_ecs::{ component::Tick, resource::Resource, - system::{Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam}, + system::{ + Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam, + SystemParamValidationError, + }, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3}; @@ -131,7 +134,7 @@ pub struct Swap(PhantomData); /// .add_systems(EndOfMyContext, end_gizmo_context::) /// .add_systems( /// Last, -/// propagate_gizmos::.before(UpdateGizmoMeshes), +/// propagate_gizmos::.before(GizmoMeshSystems), /// ); /// } /// } @@ -202,29 +205,20 @@ where } } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &bevy_ecs::archetype::Archetype, - system_meta: &mut SystemMeta, - ) { - // SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`. - unsafe { - GizmosState::::new_archetype(&mut state.state, archetype, system_meta); - }; - } - fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { GizmosState::::apply(&mut state.state, system_meta, world); } #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Delegated to existing `SystemParam` implementations. - unsafe { GizmosState::::validate_param(&state.state, system_meta, world) } + unsafe { + GizmosState::::validate_param(&mut state.state, system_meta, world) + } } #[inline] diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs old mode 100644 new mode 100755 index 373e248fb2..a59a80e89e --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -24,7 +24,7 @@ extern crate self as bevy_gizmos; /// System set label for the systems handling the rendering of gizmos. #[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)] -pub enum GizmoRenderSystem { +pub enum GizmoRenderSystems { /// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase #[cfg(feature = "bevy_sprite")] QueueLineGizmos2d, @@ -33,6 +33,10 @@ pub enum GizmoRenderSystem { QueueLineGizmos3d, } +/// Deprecated alias for [`GizmoRenderSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `GizmoRenderSystems`.")] +pub type GizmoRenderSystem = GizmoRenderSystems; + #[cfg(feature = "bevy_render")] pub mod aabb; pub mod arcs; @@ -79,13 +83,12 @@ pub mod prelude { } use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop}; -use bevy_asset::{weak_handle, Asset, AssetApp, AssetId, Assets, Handle}; +use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_ecs::{ resource::Resource, schedule::{IntoScheduleConfigs, SystemSet}, system::{Res, ResMut}, }; -use bevy_math::{Vec3, Vec4}; use bevy_reflect::TypePath; #[cfg(all( @@ -99,6 +102,7 @@ use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer}; #[cfg(feature = "bevy_render")] use { crate::retained::extract_linegizmos, + bevy_asset::AssetId, bevy_ecs::{ component::Component, entity::Entity, @@ -108,19 +112,19 @@ use { Commands, SystemParamItem, }, }, - bevy_math::{Affine3, Affine3A}, + bevy_math::{Affine3, Affine3A, Vec4}, bevy_render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, - BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, - ShaderStages, ShaderType, VertexFormat, + BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, ShaderStages, + ShaderType, VertexFormat, }, renderer::RenderDevice, sync_world::{MainEntity, TemporaryRenderEntity}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }, bytemuck::cast_slice, }; @@ -132,20 +136,14 @@ use { use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode}; use bevy_time::Fixed; use bevy_utils::TypeIdMap; -use config::{ - DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint, -}; +#[cfg(feature = "bevy_render")] +use config::GizmoLineJoint; +use config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}; use core::{any::TypeId, marker::PhantomData, mem}; use gizmos::{GizmoStorage, Swap}; #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] use light::LightGizmoPlugin; -#[cfg(feature = "bevy_render")] -const LINE_SHADER_HANDLE: Handle = weak_handle!("15dc5869-ad30-4664-b35a-4137cb8804a1"); -#[cfg(feature = "bevy_render")] -const LINE_JOINT_SHADER_HANDLE: Handle = - weak_handle!("7b5bdda5-df81-4711-a6cf-e587700de6f2"); - /// A [`Plugin`] that provides an immediate mode drawing api for visual debugging. /// /// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpritePlugin`](bevy_sprite::SpritePlugin). @@ -156,14 +154,9 @@ impl Plugin for GizmoPlugin { fn build(&self, app: &mut App) { #[cfg(feature = "bevy_render")] { - use bevy_asset::load_internal_asset; - load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - LINE_JOINT_SHADER_HANDLE, - "line_joints.wgsl", - Shader::from_wgsl - ); + use bevy_asset::embedded_asset; + embedded_asset!(app, "lines.wgsl"); + embedded_asset!(app, "line_joints.wgsl"); } app.register_type::() @@ -185,7 +178,7 @@ impl Plugin for GizmoPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems( Render, - prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), + prepare_line_gizmo_bind_group.in_set(RenderSystems::PrepareBindGroups), ); render_app.add_systems(ExtractSchedule, (extract_gizmo_data, extract_linegizmos)); @@ -268,20 +261,20 @@ impl AppGizmoBuilder for App { .add_systems( RunFixedMainLoop, start_gizmo_context:: - .in_set(bevy_app::RunFixedMainLoopSystem::BeforeFixedMainLoop), + .in_set(bevy_app::RunFixedMainLoopSystems::BeforeFixedMainLoop), ) .add_systems(FixedFirst, clear_gizmo_context::) .add_systems(FixedLast, collect_requested_gizmos::) .add_systems( RunFixedMainLoop, end_gizmo_context:: - .in_set(bevy_app::RunFixedMainLoopSystem::AfterFixedMainLoop), + .in_set(bevy_app::RunFixedMainLoopSystems::AfterFixedMainLoop), ) .add_systems( Last, ( - propagate_gizmos::.before(UpdateGizmoMeshes), - update_gizmo_meshes::.in_set(UpdateGizmoMeshes), + propagate_gizmos::.before(GizmoMeshSystems), + update_gizmo_meshes::.in_set(GizmoMeshSystems), ), ); @@ -332,7 +325,7 @@ pub fn start_gizmo_context( /// /// Pop the default gizmos context out of the [`Swap`] gizmo storage. /// -/// This must be called before [`UpdateGizmoMeshes`] in the [`Last`] schedule. +/// This must be called before [`GizmoMeshSystems`] in the [`Last`] schedule. pub fn end_gizmo_context( mut swap: ResMut>>, mut default: ResMut>, @@ -367,7 +360,7 @@ where /// Propagate the contextual gizmo into the `Update` storage for rendering. /// -/// This should be before [`UpdateGizmoMeshes`]. +/// This should be before [`GizmoMeshSystems`]. pub fn propagate_gizmos( mut update_storage: ResMut>, contextual_storage: Res>, @@ -380,7 +373,11 @@ pub fn propagate_gizmos( /// System set for updating the rendering meshes for drawing gizmos. #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub struct UpdateGizmoMeshes; +pub struct GizmoMeshSystems; + +/// Deprecated alias for [`GizmoMeshSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `GizmoMeshSystems`.")] +pub type UpdateGizmoMeshes = GizmoMeshSystems; /// Prepare gizmos for rendering. /// @@ -503,7 +500,7 @@ struct LineGizmoUniform { line_scale: f32, /// WebGL2 structs must be 16 byte aligned. #[cfg(feature = "webgl")] - _padding: Vec3, + _padding: bevy_math::Vec3, } /// A collection of gizmos. diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs index 7f7dadacc2..1bd6ee3cac 100644 --- a/crates/bevy_gizmos/src/light.rs +++ b/crates/bevy_gizmos/src/light.rs @@ -24,7 +24,7 @@ use bevy_math::{ }; use bevy_pbr::{DirectionalLight, PointLight, SpotLight}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_transform::{components::GlobalTransform, TransformSystem}; +use bevy_transform::{components::GlobalTransform, TransformSystems}; use crate::{ config::{GizmoConfigGroup, GizmoConfigStore}, @@ -126,7 +126,7 @@ impl Plugin for LightGizmoPlugin { config.config::().1.draw_all }), ) - .after(TransformSystem::TransformPropagate), + .after(TransformSystems::Propagate), ); } } diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 3a43055491..a97071249d 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -1,10 +1,11 @@ use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout, - SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE, + DrawLineJointGizmo, GizmoRenderSystems, GpuLineGizmo, LineGizmoUniformBindgroupLayout, + SetLineGizmoBindGroup, }; use bevy_app::{App, Plugin}; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}; use bevy_ecs::{ @@ -25,7 +26,7 @@ use bevy_render::{ }, render_resource::*, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup}; use tracing::error; @@ -46,8 +47,8 @@ impl Plugin for LineGizmo2dPlugin { .init_resource::>() .configure_sets( Render, - GizmoRenderSystem::QueueLineGizmos2d - .in_set(RenderSet::Queue) + GizmoRenderSystems::QueueLineGizmos2d + .in_set(RenderSystems::Queue) .ambiguous_with(bevy_sprite::queue_sprites) .ambiguous_with( bevy_sprite::queue_material2d_meshes::, @@ -56,7 +57,7 @@ impl Plugin for LineGizmo2dPlugin { .add_systems( Render, (queue_line_gizmos_2d, queue_line_joint_gizmos_2d) - .in_set(GizmoRenderSystem::QueueLineGizmos2d) + .in_set(GizmoRenderSystems::QueueLineGizmos2d) .after(prepare_assets::), ); } @@ -75,6 +76,7 @@ impl Plugin for LineGizmo2dPlugin { struct LineGizmoPipeline { mesh_pipeline: Mesh2dPipeline, uniform_layout: BindGroupLayout, + shader: Handle, } impl FromWorld for LineGizmoPipeline { @@ -85,6 +87,7 @@ impl FromWorld for LineGizmoPipeline { .resource::() .layout .clone(), + shader: load_embedded_asset!(render_world, "lines.wgsl"), } } } @@ -124,13 +127,13 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: LINE_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: line_gizmo_vertex_buffer_layouts(key.strip), }, fragment: Some(FragmentState { - shader: LINE_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: fragment_entry_point.into(), targets: vec![Some(ColorTargetState { @@ -173,6 +176,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { struct LineJointGizmoPipeline { mesh_pipeline: Mesh2dPipeline, uniform_layout: BindGroupLayout, + shader: Handle, } impl FromWorld for LineJointGizmoPipeline { @@ -183,6 +187,7 @@ impl FromWorld for LineJointGizmoPipeline { .resource::() .layout .clone(), + shader: load_embedded_asset!(render_world, "line_joints.wgsl"), } } } @@ -225,13 +230,13 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: LINE_JOINT_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: entry_point.into(), shader_defs: shader_defs.clone(), buffers: line_joint_gizmo_vertex_buffer_layouts(), }, fragment: Some(FragmentState { - shader: LINE_JOINT_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 799793e6cb..1cc70c67cb 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -1,10 +1,11 @@ use crate::{ config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig}, line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo, - DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout, - SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE, + DrawLineJointGizmo, GizmoRenderSystems, GpuLineGizmo, LineGizmoUniformBindgroupLayout, + SetLineGizmoBindGroup, }; use bevy_app::{App, Plugin}; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}, oit::OrderIndependentTransparencySettings, @@ -30,7 +31,7 @@ use bevy_render::{ }, render_resource::*, view::{ExtractedView, Msaa, RenderLayers, ViewTarget}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use tracing::error; @@ -49,14 +50,14 @@ impl Plugin for LineGizmo3dPlugin { .init_resource::>() .configure_sets( Render, - GizmoRenderSystem::QueueLineGizmos3d - .in_set(RenderSet::Queue) + GizmoRenderSystems::QueueLineGizmos3d + .in_set(RenderSystems::Queue) .ambiguous_with(bevy_pbr::queue_material_meshes::), ) .add_systems( Render, (queue_line_gizmos_3d, queue_line_joint_gizmos_3d) - .in_set(GizmoRenderSystem::QueueLineGizmos3d) + .in_set(GizmoRenderSystems::QueueLineGizmos3d) .after(prepare_assets::), ); } @@ -75,6 +76,7 @@ impl Plugin for LineGizmo3dPlugin { struct LineGizmoPipeline { mesh_pipeline: MeshPipeline, uniform_layout: BindGroupLayout, + shader: Handle, } impl FromWorld for LineGizmoPipeline { @@ -85,6 +87,7 @@ impl FromWorld for LineGizmoPipeline { .resource::() .layout .clone(), + shader: load_embedded_asset!(render_world, "lines.wgsl"), } } } @@ -131,13 +134,13 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: LINE_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: line_gizmo_vertex_buffer_layouts(key.strip), }, fragment: Some(FragmentState { - shader: LINE_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: fragment_entry_point.into(), targets: vec![Some(ColorTargetState { @@ -171,6 +174,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { struct LineJointGizmoPipeline { mesh_pipeline: MeshPipeline, uniform_layout: BindGroupLayout, + shader: Handle, } impl FromWorld for LineJointGizmoPipeline { @@ -181,6 +185,7 @@ impl FromWorld for LineJointGizmoPipeline { .resource::() .layout .clone(), + shader: load_embedded_asset!(render_world, "line_joints.wgsl"), } } } @@ -230,13 +235,13 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: LINE_JOINT_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: entry_point.into(), shader_defs: shader_defs.clone(), buffers: line_joint_gizmo_vertex_buffer_layouts(), }, fragment: Some(FragmentState { - shader: LINE_JOINT_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index d37fb2a1f3..cc67047c23 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -dds = ["bevy_render/dds", "bevy_image/dds", "bevy_core_pipeline/dds"] pbr_transmission_textures = ["bevy_pbr/pbr_transmission_textures"] pbr_multi_layer_material_textures = [ "bevy_pbr/pbr_multi_layer_material_textures", @@ -27,6 +26,7 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } +bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev" } @@ -36,7 +36,7 @@ bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", features = [ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } @@ -61,7 +61,7 @@ fixedbitset = "0.5" itertools = "0.14" percent-encoding = "2.1" serde = { version = "1.0", features = ["derive"] } -serde_json = "1" +serde_json = "1.0.140" smallvec = "1.11" tracing = { version = "0.1", default-features = false, features = ["std"] } diff --git a/crates/bevy_gltf/src/assets.rs b/crates/bevy_gltf/src/assets.rs index de2ee44bf7..bfc920ebce 100644 --- a/crates/bevy_gltf/src/assets.rs +++ b/crates/bevy_gltf/src/assets.rs @@ -1,13 +1,15 @@ //! Representation of assets present in a glTF file +use core::ops::Deref; + #[cfg(feature = "bevy_animation")] use bevy_animation::AnimationClip; use bevy_asset::{Asset, Handle}; use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_mesh::{skinning::SkinnedMeshInverseBindposes, Mesh}; use bevy_pbr::StandardMaterial; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; -use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, Mesh}; use bevy_scene::Scene; use crate::GltfAssetLabel; @@ -214,7 +216,7 @@ impl GltfPrimitive { } } -/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes) +/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_mesh::skinning::SkinnedMeshInverseBindposes) /// and an optional [`GltfExtras`]. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin). @@ -297,6 +299,21 @@ pub struct GltfMeshExtras { pub value: String, } +/// The mesh name of a glTF primitive. +/// +/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh). +#[derive(Clone, Debug, Reflect, Default, Component)] +#[reflect(Component, Clone)] +pub struct GltfMeshName(pub String); + +impl Deref for GltfMeshName { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + /// Additional untyped data that can be present on most glTF types at the material level. /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). @@ -313,3 +330,11 @@ pub struct GltfMaterialExtras { #[derive(Clone, Debug, Reflect, Default, Component)] #[reflect(Component, Clone)] pub struct GltfMaterialName(pub String); + +impl Deref for GltfMaterialName { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} diff --git a/crates/bevy_gltf/src/label.rs b/crates/bevy_gltf/src/label.rs index 3a1ddfdf10..b74d5ab2d6 100644 --- a/crates/bevy_gltf/src/label.rs +++ b/crates/bevy_gltf/src/label.rs @@ -37,7 +37,7 @@ pub enum GltfAssetLabel { Node(usize), /// `Mesh{}`: glTF Mesh as a [`GltfMesh`](crate::GltfMesh) Mesh(usize), - /// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`](bevy_render::mesh::Mesh) + /// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`](bevy_mesh::Mesh) Primitive { /// Index of the mesh for this primitive mesh: usize, @@ -70,7 +70,7 @@ pub enum GltfAssetLabel { /// `Skin{}`: glTF mesh skin as [`GltfSkin`](crate::GltfSkin) Skin(usize), /// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy - /// [`SkinnedMeshInverseBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes) + /// [`SkinnedMeshInverseBindposes`](bevy_mesh::skinning::SkinnedMeshInverseBindposes) InverseBindMatrices(usize), } diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 159cdf4c67..87818a21c2 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -97,12 +97,17 @@ mod vertex_attributes; extern crate alloc; -use bevy_platform_support::collections::HashMap; +use alloc::sync::Arc; +use std::sync::Mutex; +use tracing::warn; + +use bevy_platform::collections::HashMap; use bevy_app::prelude::*; use bevy_asset::AssetApp; -use bevy_image::CompressedImageFormats; -use bevy_render::{mesh::MeshVertexAttribute, renderer::RenderDevice}; +use bevy_ecs::prelude::Resource; +use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor}; +use bevy_mesh::MeshVertexAttribute; /// The glTF prelude. /// @@ -114,10 +119,57 @@ pub mod prelude { pub use {assets::*, label::GltfAssetLabel, loader::*}; +// Has to store an Arc> as there is no other way to mutate fields of asset loaders. +/// Stores default [`ImageSamplerDescriptor`] in main world. +#[derive(Resource)] +pub struct DefaultGltfImageSampler(Arc>); + +impl DefaultGltfImageSampler { + /// Creates a new [`DefaultGltfImageSampler`]. + pub fn new(descriptor: &ImageSamplerDescriptor) -> Self { + Self(Arc::new(Mutex::new(descriptor.clone()))) + } + + /// Returns the current default [`ImageSamplerDescriptor`]. + pub fn get(&self) -> ImageSamplerDescriptor { + self.0.lock().unwrap().clone() + } + + /// Makes a clone of internal [`Arc`] pointer. + /// + /// Intended only to be used by code with no access to ECS. + pub fn get_internal(&self) -> Arc> { + self.0.clone() + } + + /// Replaces default [`ImageSamplerDescriptor`]. + /// + /// Doesn't apply to samplers already built on top of it, i.e. `GltfLoader`'s output. + /// Assets need to manually be reloaded. + pub fn set(&self, descriptor: &ImageSamplerDescriptor) { + *self.0.lock().unwrap() = descriptor.clone(); + } +} + /// Adds support for glTF file loading to the app. -#[derive(Default)] pub struct GltfPlugin { - custom_vertex_attributes: HashMap, MeshVertexAttribute>, + /// The default image sampler to lay glTF sampler data on top of. + /// + /// Can be modified with [`DefaultGltfImageSampler`] resource. + pub default_sampler: ImageSamplerDescriptor, + /// Registry for custom vertex attributes. + /// + /// To specify, use [`GltfPlugin::add_custom_vertex_attribute`]. + pub custom_vertex_attributes: HashMap, MeshVertexAttribute>, +} + +impl Default for GltfPlugin { + fn default() -> Self { + GltfPlugin { + default_sampler: ImageSamplerDescriptor::linear(), + custom_vertex_attributes: HashMap::default(), + } + } } impl GltfPlugin { @@ -141,6 +193,7 @@ impl Plugin for GltfPlugin { app.register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .init_asset::() @@ -152,13 +205,23 @@ impl Plugin for GltfPlugin { } fn finish(&self, app: &mut App) { - let supported_compressed_formats = match app.world().get_resource::() { - Some(render_device) => CompressedImageFormats::from_features(render_device.features()), - None => CompressedImageFormats::NONE, + let supported_compressed_formats = if let Some(resource) = + app.world().get_resource::() + { + resource.0 + } else { + warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \ + RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend."); + CompressedImageFormats::NONE }; + + let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler); + let default_sampler = default_sampler_resource.get_internal(); + app.insert_resource(default_sampler_resource); app.register_asset_loader(GltfLoader { supported_compressed_formats, custom_vertex_attributes: self.custom_vertex_attributes.clone(), + default_sampler, }); } } diff --git a/crates/bevy_gltf/src/loader/gltf_ext/mesh.rs b/crates/bevy_gltf/src/loader/gltf_ext/mesh.rs index 90c838b468..60a153fed3 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/mesh.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/mesh.rs @@ -1,22 +1,29 @@ -use bevy_render::mesh::PrimitiveTopology; +use bevy_mesh::PrimitiveTopology; -use gltf::mesh::{Mesh, Mode, Primitive}; +use gltf::{ + mesh::{Mesh, Mode}, + Material, +}; use crate::GltfError; -pub(crate) fn primitive_name(mesh: &Mesh<'_>, primitive: &Primitive) -> String { +pub(crate) fn primitive_name(mesh: &Mesh<'_>, material: &Material) -> String { let mesh_name = mesh.name().unwrap_or("Mesh"); - if mesh.primitives().len() > 1 { - format!("{}.{}", mesh_name, primitive.index()) + + if let Some(material_name) = material.name() { + format!("{}.{}", mesh_name, material_name) } else { mesh_name.to_string() } } /// Maps the `primitive_topology` from glTF to `wgpu`. -#[expect( - clippy::result_large_err, - reason = "`GltfError` is only barely past the threshold for large errors." +#[cfg_attr( + not(target_arch = "wasm32"), + expect( + clippy::result_large_err, + reason = "`GltfError` is only barely past the threshold for large errors." + ) )] pub(crate) fn primitive_topology(mode: Mode) -> Result { match mode { diff --git a/crates/bevy_gltf/src/loader/gltf_ext/mod.rs b/crates/bevy_gltf/src/loader/gltf_ext/mod.rs index 558ed645dd..6036948d9c 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/mod.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/mod.rs @@ -5,7 +5,7 @@ pub mod mesh; pub mod scene; pub mod texture; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use fixedbitset::FixedBitSet; use gltf::{Document, Gltf}; @@ -14,9 +14,12 @@ use super::GltfError; use self::{material::extension_texture_index, scene::check_is_part_of_cycle}; -#[expect( - clippy::result_large_err, - reason = "need to be signature compatible with `load_gltf`" +#[cfg_attr( + not(target_arch = "wasm32"), + expect( + clippy::result_large_err, + reason = "need to be signature compatible with `load_gltf`" + ) )] /// Checks all glTF nodes for cycles, starting at the scene root. pub(crate) fn check_for_cycles(gltf: &Gltf) -> Result<(), GltfError> { diff --git a/crates/bevy_gltf/src/loader/gltf_ext/scene.rs b/crates/bevy_gltf/src/loader/gltf_ext/scene.rs index 7845280b18..83e6778b99 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/scene.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/scene.rs @@ -8,7 +8,7 @@ use fixedbitset::FixedBitSet; use itertools::Itertools; #[cfg(feature = "bevy_animation")] -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use crate::GltfError; @@ -43,9 +43,12 @@ pub(crate) fn node_transform(node: &Node) -> Transform { } } -#[expect( - clippy::result_large_err, - reason = "need to be signature compatible with `load_gltf`" +#[cfg_attr( + not(target_arch = "wasm32"), + expect( + clippy::result_large_err, + reason = "need to be signature compatible with `load_gltf`" + ) )] /// Check if [`Node`] is part of cycle pub(crate) fn check_is_part_of_cycle( diff --git a/crates/bevy_gltf/src/loader/gltf_ext/texture.rs b/crates/bevy_gltf/src/loader/gltf_ext/texture.rs index 5fb5bcce0d..0ea16936a6 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/texture.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/texture.rs @@ -39,48 +39,48 @@ pub(crate) fn texture_handle( } /// Extracts the texture sampler data from the glTF [`Texture`]. -pub(crate) fn texture_sampler(texture: &Texture<'_>) -> ImageSamplerDescriptor { +pub(crate) fn texture_sampler( + texture: &Texture<'_>, + default_sampler: &ImageSamplerDescriptor, +) -> ImageSamplerDescriptor { let gltf_sampler = texture.sampler(); + let mut sampler = default_sampler.clone(); - ImageSamplerDescriptor { - address_mode_u: address_mode(&gltf_sampler.wrap_s()), - address_mode_v: address_mode(&gltf_sampler.wrap_t()), + sampler.address_mode_u = address_mode(&gltf_sampler.wrap_s()); + sampler.address_mode_v = address_mode(&gltf_sampler.wrap_t()); - mag_filter: gltf_sampler - .mag_filter() - .map(|mf| match mf { - MagFilter::Nearest => ImageFilterMode::Nearest, - MagFilter::Linear => ImageFilterMode::Linear, - }) - .unwrap_or(ImageSamplerDescriptor::default().mag_filter), - - min_filter: gltf_sampler - .min_filter() - .map(|mf| match mf { - MinFilter::Nearest - | MinFilter::NearestMipmapNearest - | MinFilter::NearestMipmapLinear => ImageFilterMode::Nearest, - MinFilter::Linear - | MinFilter::LinearMipmapNearest - | MinFilter::LinearMipmapLinear => ImageFilterMode::Linear, - }) - .unwrap_or(ImageSamplerDescriptor::default().min_filter), - - mipmap_filter: gltf_sampler - .min_filter() - .map(|mf| match mf { - MinFilter::Nearest - | MinFilter::Linear - | MinFilter::NearestMipmapNearest - | MinFilter::LinearMipmapNearest => ImageFilterMode::Nearest, - MinFilter::NearestMipmapLinear | MinFilter::LinearMipmapLinear => { - ImageFilterMode::Linear - } - }) - .unwrap_or(ImageSamplerDescriptor::default().mipmap_filter), - - ..Default::default() + // Shouldn't parse filters when anisotropic filtering is on, because trilinear is then required by wgpu. + // We also trust user to have provided a valid sampler. + if sampler.anisotropy_clamp == 1 { + if let Some(mag_filter) = gltf_sampler.mag_filter().map(|mf| match mf { + MagFilter::Nearest => ImageFilterMode::Nearest, + MagFilter::Linear => ImageFilterMode::Linear, + }) { + sampler.mag_filter = mag_filter; + } + if let Some(min_filter) = gltf_sampler.min_filter().map(|mf| match mf { + MinFilter::Nearest + | MinFilter::NearestMipmapNearest + | MinFilter::NearestMipmapLinear => ImageFilterMode::Nearest, + MinFilter::Linear | MinFilter::LinearMipmapNearest | MinFilter::LinearMipmapLinear => { + ImageFilterMode::Linear + } + }) { + sampler.min_filter = min_filter; + } + if let Some(mipmap_filter) = gltf_sampler.min_filter().map(|mf| match mf { + MinFilter::Nearest + | MinFilter::Linear + | MinFilter::NearestMipmapNearest + | MinFilter::LinearMipmapNearest => ImageFilterMode::Nearest, + MinFilter::NearestMipmapLinear | MinFilter::LinearMipmapLinear => { + ImageFilterMode::Linear + } + }) { + sampler.mipmap_filter = mipmap_filter; + } } + sampler } pub(crate) fn texture_label(texture: &Texture<'_>) -> GltfAssetLabel { diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 9d400e44bc..9bdeb23f26 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -1,20 +1,23 @@ mod extensions; mod gltf_ext; +use alloc::sync::Arc; use std::{ io::Error, path::{Path, PathBuf}, + sync::Mutex, }; #[cfg(feature = "bevy_animation")] use bevy_animation::{prelude::*, AnimationTarget, AnimationTargetId}; use bevy_asset::{ io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError, + RenderAssetUsages, }; use bevy_color::{Color, LinearRgba}; use bevy_core_pipeline::prelude::Camera3d; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, hierarchy::ChildSpawner, name::Name, world::World, @@ -24,22 +27,22 @@ use bevy_image::{ ImageType, TextureError, }; use bevy_math::{Mat4, Vec3}; +use bevy_mesh::{ + morph::{MeshMorphWeights, MorphAttributes, MorphTargetImage, MorphWeights}, + skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, + Indices, Mesh, MeshVertexAttribute, PrimitiveTopology, VertexAttributeValues, +}; #[cfg(feature = "pbr_transmission_textures")] use bevy_pbr::UvChannel; use bevy_pbr::{ DirectionalLight, MeshMaterial3d, PointLight, SpotLight, StandardMaterial, MAX_JOINTS, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection, ScalingMode}, - mesh::{ - morph::{MeshMorphWeights, MorphAttributes, MorphTargetImage, MorphWeights}, - skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - Indices, Mesh, Mesh3d, MeshVertexAttribute, VertexAttributeValues, - }, + mesh::Mesh3d, primitives::Aabb, - render_asset::RenderAssetUsages, - render_resource::{Face, PrimitiveTopology}, + render_resource::Face, view::Visibility, }; use bevy_scene::Scene; @@ -63,9 +66,11 @@ use tracing::{error, info_span, warn}; use crate::{ vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras, - GltfMaterialName, GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin, + GltfMaterialName, GltfMeshExtras, GltfMeshName, GltfNode, GltfSceneExtras, GltfSkin, }; +#[cfg(feature = "bevy_animation")] +use self::gltf_ext::scene::collect_path; use self::{ extensions::{AnisotropyExtension, ClearcoatExtension, SpecularExtension}, gltf_ext::{ @@ -75,7 +80,7 @@ use self::{ warn_on_differing_texture_transforms, }, mesh::{primitive_name, primitive_topology}, - scene::{collect_path, node_name, node_transform}, + scene::{node_name, node_transform}, texture::{texture_handle, texture_sampler, texture_transform_to_affine2}, }, }; @@ -120,10 +125,10 @@ pub enum GltfError { MissingAnimationSampler(usize), /// Failed to generate tangents. #[error("failed to generate tangents: {0}")] - GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError), + GenerateTangentsError(#[from] bevy_mesh::GenerateTangentsError), /// Failed to generate morph targets. #[error("failed to generate morph targets: {0}")] - MorphTarget(#[from] bevy_render::mesh::morph::MorphBuildError), + MorphTarget(#[from] bevy_mesh::morph::MorphBuildError), /// Circular children in Nodes #[error("GLTF model must be a tree, found cycle instead at node indices: {0:?}")] #[from(ignore)] @@ -143,6 +148,8 @@ pub struct GltfLoader { /// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) /// for additional details on custom attributes. pub custom_vertex_attributes: HashMap, MeshVertexAttribute>, + /// Arc to default [`ImageSamplerDescriptor`]. + pub default_sampler: Arc>, } /// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of @@ -178,6 +185,12 @@ pub struct GltfLoaderSettings { pub load_lights: bool, /// If true, the loader will include the root of the gltf root node. pub include_source: bool, + /// Overrides the default sampler. Data from sampler node is added on top of that. + /// + /// If None, uses global default which is stored in `DefaultGltfImageSampler` resource. + pub default_sampler: Option, + /// If true, the loader will ignore sampler data from gltf and use the default sampler. + pub override_sampler: bool, } impl Default for GltfLoaderSettings { @@ -188,6 +201,8 @@ impl Default for GltfLoaderSettings { load_cameras: true, load_lights: true, include_source: false, + default_sampler: None, + override_sampler: false, } } } @@ -491,12 +506,10 @@ async fn load_gltf<'a, 'b, 'c>( ); } } - let handle = load_context - .add_labeled_asset( - GltfAssetLabel::Animation(animation.index()).to_string(), - animation_clip, - ) - .expect("animation indices are unique, so the label is unique"); + let handle = load_context.add_labeled_asset( + GltfAssetLabel::Animation(animation.index()).to_string(), + animation_clip, + ); if let Some(name) = animation.name() { named_animations.insert(name.into(), handle.clone()); } @@ -505,6 +518,10 @@ async fn load_gltf<'a, 'b, 'c>( (animations, named_animations, animation_roots) }; + let default_sampler = match settings.default_sampler.as_ref() { + Some(sampler) => sampler, + None => &loader.default_sampler.lock().unwrap().clone(), + }; // We collect handles to ensure loaded images from paths are not unloaded before they are used elsewhere // in the loader. This prevents "reloads", but it also prevents dropping the is_srgb context on reload. // @@ -521,7 +538,8 @@ async fn load_gltf<'a, 'b, 'c>( &linear_textures, parent_path, loader.supported_compressed_formats, - settings.load_materials, + default_sampler, + settings, ) .await?; image.process_loaded_texture(load_context, &mut _texture_handles); @@ -541,7 +559,8 @@ async fn load_gltf<'a, 'b, 'c>( linear_textures, parent_path, loader.supported_compressed_formats, - settings.load_materials, + default_sampler, + settings, ) .await }); @@ -643,8 +662,7 @@ async fn load_gltf<'a, 'b, 'c>( RenderAssetUsages::default(), )?; let handle = load_context - .add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0) - .expect("morph target indices are unique, so the label is unique"); + .add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0); mesh.set_morph_targets(handle); let extras = gltf_mesh.extras().as_ref(); @@ -697,9 +715,7 @@ async fn load_gltf<'a, 'b, 'c>( }); } - let mesh_handle = load_context - .add_labeled_asset(primitive_label.to_string(), mesh) - .expect("primitive indices are unique, so the label is unique"); + let mesh_handle = load_context.add_labeled_asset(primitive_label.to_string(), mesh); primitives.push(super::GltfPrimitive::new( &gltf_mesh, &primitive, @@ -723,9 +739,7 @@ async fn load_gltf<'a, 'b, 'c>( gltf_mesh.extras().as_deref().map(GltfExtras::from), ); - let handle = load_context - .add_labeled_asset(mesh.asset_label().to_string(), mesh) - .expect("mesh indices are unique, so the label is unique"); + let handle = load_context.add_labeled_asset(mesh.asset_label().to_string(), mesh); if let Some(name) = gltf_mesh.name() { named_meshes.insert(name.into(), handle.clone()); } @@ -738,16 +752,15 @@ async fn load_gltf<'a, 'b, 'c>( let reader = gltf_skin.reader(|buffer| Some(&buffer_data[buffer.index()])); let local_to_bone_bind_matrices: Vec = reader .read_inverse_bind_matrices() - .unwrap() - .map(|mat| Mat4::from_cols_array_2d(&mat)) - .collect(); + .map(|mats| mats.map(|mat| Mat4::from_cols_array_2d(&mat)).collect()) + .unwrap_or_else(|| { + core::iter::repeat_n(Mat4::IDENTITY, gltf_skin.joints().len()).collect() + }); - load_context - .add_labeled_asset( - GltfAssetLabel::InverseBindMatrices(gltf_skin.index()).to_string(), - SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices), - ) - .expect("inverse bind matrix indices are unique, so the label is unique") + load_context.add_labeled_asset( + GltfAssetLabel::InverseBindMatrices(gltf_skin.index()).to_string(), + SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices), + ) }) .collect(); @@ -796,8 +809,7 @@ async fn load_gltf<'a, 'b, 'c>( ); let handle = load_context - .add_labeled_asset(gltf_skin.asset_label().to_string(), gltf_skin) - .expect("skin indices are unique, so the label is unique"); + .add_labeled_asset(gltf_skin.asset_label().to_string(), gltf_skin); if let Some(name) = skin.name() { named_skins.insert(name.into(), handle.clone()); @@ -830,9 +842,7 @@ async fn load_gltf<'a, 'b, 'c>( #[cfg(feature = "bevy_animation")] let gltf_node = gltf_node.with_animation_root(animation_roots.contains(&node.index())); - let handle = load_context - .add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node) - .expect("node indices are unique, so the label is unique"); + let handle = load_context.add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node); nodes.insert(node.index(), handle.clone()); if let Some(name) = node.name() { named_nodes.insert(name.into(), handle); @@ -921,12 +931,10 @@ async fn load_gltf<'a, 'b, 'c>( }); } let loaded_scene = scene_load_context.finish(Scene::new(world)); - let scene_handle = load_context - .add_loaded_labeled_asset( - GltfAssetLabel::Scene(scene.index()).to_string(), - loaded_scene, - ) - .expect("scene indices are unique, so the label is unique"); + let scene_handle = load_context.add_loaded_labeled_asset( + GltfAssetLabel::Scene(scene.index()).to_string(), + loaded_scene, + ); if let Some(name) = scene.name() { named_scenes.insert(name.into(), scene_handle.clone()); @@ -968,28 +976,28 @@ async fn load_image<'a, 'b>( linear_textures: &HashSet, parent_path: &'b Path, supported_compressed_formats: CompressedImageFormats, - render_asset_usages: RenderAssetUsages, + default_sampler: &ImageSamplerDescriptor, + settings: &GltfLoaderSettings, ) -> Result { let is_srgb = !linear_textures.contains(&gltf_texture.index()); - let sampler_descriptor = texture_sampler(&gltf_texture); - #[cfg(all(debug_assertions, feature = "dds"))] - let name = gltf_texture - .name() - .map_or("Unknown GLTF Texture".to_string(), ToString::to_string); + let sampler_descriptor = if settings.override_sampler { + default_sampler.clone() + } else { + texture_sampler(&gltf_texture, default_sampler) + }; + match gltf_texture.source().source() { Source::View { view, mime_type } => { let start = view.offset(); let end = view.offset() + view.length(); let buffer = &buffer_data[view.buffer().index()][start..end]; let image = Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - name, buffer, ImageType::MimeType(mime_type), supported_compressed_formats, is_srgb, ImageSampler::Descriptor(sampler_descriptor), - render_asset_usages, + settings.load_materials, )?; Ok(ImageOrPath::Image { image, @@ -1006,14 +1014,12 @@ async fn load_image<'a, 'b>( let image_type = ImageType::MimeType(data_uri.mime_type); Ok(ImageOrPath::Image { image: Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - name, &bytes, mime_type.map(ImageType::MimeType).unwrap_or(image_type), supported_compressed_formats, is_srgb, ImageSampler::Descriptor(sampler_descriptor), - render_asset_usages, + settings.load_materials, )?, label: GltfAssetLabel::Texture(gltf_texture.index()), }) @@ -1037,75 +1043,71 @@ fn load_material( is_scale_inverted: bool, ) -> Handle { let material_label = material_label(material, is_scale_inverted); - load_context - .labeled_asset_scope(material_label.to_string(), |load_context| { - let pbr = material.pbr_metallic_roughness(); + load_context.labeled_asset_scope(material_label.to_string(), |load_context| { + let pbr = material.pbr_metallic_roughness(); - // TODO: handle missing label handle errors here? - let color = pbr.base_color_factor(); - let base_color_channel = pbr - .base_color_texture() - .map(|info| uv_channel(material, "base color", info.tex_coord())) - .unwrap_or_default(); - let base_color_texture = pbr - .base_color_texture() - .map(|info| texture_handle(&info.texture(), load_context)); + // TODO: handle missing label handle errors here? + let color = pbr.base_color_factor(); + let base_color_channel = pbr + .base_color_texture() + .map(|info| uv_channel(material, "base color", info.tex_coord())) + .unwrap_or_default(); + let base_color_texture = pbr + .base_color_texture() + .map(|info| texture_handle(&info.texture(), load_context)); - let uv_transform = pbr - .base_color_texture() - .and_then(|info| info.texture_transform().map(texture_transform_to_affine2)) - .unwrap_or_default(); + let uv_transform = pbr + .base_color_texture() + .and_then(|info| info.texture_transform().map(texture_transform_to_affine2)) + .unwrap_or_default(); - let normal_map_channel = material - .normal_texture() - .map(|info| uv_channel(material, "normal map", info.tex_coord())) - .unwrap_or_default(); - let normal_map_texture: Option> = - material.normal_texture().map(|normal_texture| { - // TODO: handle normal_texture.scale - texture_handle(&normal_texture.texture(), load_context) - }); - - let metallic_roughness_channel = pbr - .metallic_roughness_texture() - .map(|info| uv_channel(material, "metallic/roughness", info.tex_coord())) - .unwrap_or_default(); - let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| { - warn_on_differing_texture_transforms( - material, - &info, - uv_transform, - "metallic/roughness", - ); - texture_handle(&info.texture(), load_context) + let normal_map_channel = material + .normal_texture() + .map(|info| uv_channel(material, "normal map", info.tex_coord())) + .unwrap_or_default(); + let normal_map_texture: Option> = + material.normal_texture().map(|normal_texture| { + // TODO: handle normal_texture.scale + texture_handle(&normal_texture.texture(), load_context) }); - let occlusion_channel = material - .occlusion_texture() - .map(|info| uv_channel(material, "occlusion", info.tex_coord())) - .unwrap_or_default(); - let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| { - // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) - texture_handle(&occlusion_texture.texture(), load_context) - }); + let metallic_roughness_channel = pbr + .metallic_roughness_texture() + .map(|info| uv_channel(material, "metallic/roughness", info.tex_coord())) + .unwrap_or_default(); + let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| { + warn_on_differing_texture_transforms( + material, + &info, + uv_transform, + "metallic/roughness", + ); + texture_handle(&info.texture(), load_context) + }); - let emissive = material.emissive_factor(); - let emissive_channel = material - .emissive_texture() - .map(|info| uv_channel(material, "emissive", info.tex_coord())) - .unwrap_or_default(); - let emissive_texture = material.emissive_texture().map(|info| { - // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) - warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive"); - texture_handle(&info.texture(), load_context) - }); + let occlusion_channel = material + .occlusion_texture() + .map(|info| uv_channel(material, "occlusion", info.tex_coord())) + .unwrap_or_default(); + let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| { + // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) + texture_handle(&occlusion_texture.texture(), load_context) + }); - #[cfg(feature = "pbr_transmission_textures")] - let ( - specular_transmission, - specular_transmission_channel, - specular_transmission_texture, - ) = material + let emissive = material.emissive_factor(); + let emissive_channel = material + .emissive_texture() + .map(|info| uv_channel(material, "emissive", info.tex_coord())) + .unwrap_or_default(); + let emissive_texture = material.emissive_texture().map(|info| { + // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) + warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive"); + texture_handle(&info.texture(), load_context) + }); + + #[cfg(feature = "pbr_transmission_textures")] + let (specular_transmission, specular_transmission_channel, specular_transmission_texture) = + material .transmission() .map_or((0.0, UvChannel::Uv0, None), |transmission| { let specular_transmission_channel = transmission @@ -1125,162 +1127,161 @@ fn load_material( ) }); - #[cfg(not(feature = "pbr_transmission_textures"))] - let specular_transmission = material - .transmission() - .map_or(0.0, |transmission| transmission.transmission_factor()); + #[cfg(not(feature = "pbr_transmission_textures"))] + let specular_transmission = material + .transmission() + .map_or(0.0, |transmission| transmission.transmission_factor()); - #[cfg(feature = "pbr_transmission_textures")] - let ( - thickness, - thickness_channel, - thickness_texture, - attenuation_distance, - attenuation_color, - ) = material.volume().map_or( - (0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]), - |volume| { - let thickness_channel = volume - .thickness_texture() - .map(|info| uv_channel(material, "thickness", info.tex_coord())) - .unwrap_or_default(); - let thickness_texture: Option> = - volume.thickness_texture().map(|thickness_texture| { - texture_handle(&thickness_texture.texture(), load_context) - }); + #[cfg(feature = "pbr_transmission_textures")] + let ( + thickness, + thickness_channel, + thickness_texture, + attenuation_distance, + attenuation_color, + ) = material.volume().map_or( + (0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]), + |volume| { + let thickness_channel = volume + .thickness_texture() + .map(|info| uv_channel(material, "thickness", info.tex_coord())) + .unwrap_or_default(); + let thickness_texture: Option> = + volume.thickness_texture().map(|thickness_texture| { + texture_handle(&thickness_texture.texture(), load_context) + }); + ( + volume.thickness_factor(), + thickness_channel, + thickness_texture, + volume.attenuation_distance(), + volume.attenuation_color(), + ) + }, + ); + + #[cfg(not(feature = "pbr_transmission_textures"))] + let (thickness, attenuation_distance, attenuation_color) = + material + .volume() + .map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| { ( volume.thickness_factor(), - thickness_channel, - thickness_texture, volume.attenuation_distance(), volume.attenuation_color(), ) - }, - ); + }); - #[cfg(not(feature = "pbr_transmission_textures"))] - let (thickness, attenuation_distance, attenuation_color) = - material - .volume() - .map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| { - ( - volume.thickness_factor(), - volume.attenuation_distance(), - volume.attenuation_color(), - ) - }); + let ior = material.ior().unwrap_or(1.5); - let ior = material.ior().unwrap_or(1.5); + // Parse the `KHR_materials_clearcoat` extension data if necessary. + let clearcoat = + ClearcoatExtension::parse(load_context, document, material).unwrap_or_default(); - // Parse the `KHR_materials_clearcoat` extension data if necessary. - let clearcoat = - ClearcoatExtension::parse(load_context, document, material).unwrap_or_default(); + // Parse the `KHR_materials_anisotropy` extension data if necessary. + let anisotropy = + AnisotropyExtension::parse(load_context, document, material).unwrap_or_default(); - // Parse the `KHR_materials_anisotropy` extension data if necessary. - let anisotropy = - AnisotropyExtension::parse(load_context, document, material).unwrap_or_default(); + // Parse the `KHR_materials_specular` extension data if necessary. + let specular = + SpecularExtension::parse(load_context, document, material).unwrap_or_default(); - // Parse the `KHR_materials_specular` extension data if necessary. - let specular = - SpecularExtension::parse(load_context, document, material).unwrap_or_default(); + // We need to operate in the Linear color space and be willing to exceed 1.0 in our channels + let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]); + let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0); - // We need to operate in the Linear color space and be willing to exceed 1.0 in our channels - let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]); - let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0); - - StandardMaterial { - base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]), - base_color_channel, - base_color_texture, - perceptual_roughness: pbr.roughness_factor(), - metallic: pbr.metallic_factor(), - metallic_roughness_channel, - metallic_roughness_texture, - normal_map_channel, - normal_map_texture, - double_sided: material.double_sided(), - cull_mode: if material.double_sided() { - None - } else if is_scale_inverted { - Some(Face::Front) - } else { - Some(Face::Back) - }, - occlusion_channel, - occlusion_texture, - emissive, - emissive_channel, - emissive_texture, - specular_transmission, - #[cfg(feature = "pbr_transmission_textures")] - specular_transmission_channel, - #[cfg(feature = "pbr_transmission_textures")] - specular_transmission_texture, - thickness, - #[cfg(feature = "pbr_transmission_textures")] - thickness_channel, - #[cfg(feature = "pbr_transmission_textures")] - thickness_texture, - ior, - attenuation_distance, - attenuation_color: Color::linear_rgb( - attenuation_color[0], - attenuation_color[1], - attenuation_color[2], - ), - unlit: material.unlit(), - alpha_mode: alpha_mode(material), - uv_transform, - clearcoat: clearcoat.clearcoat_factor.unwrap_or_default() as f32, - clearcoat_perceptual_roughness: clearcoat - .clearcoat_roughness_factor - .unwrap_or_default() as f32, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_channel: clearcoat.clearcoat_channel, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_texture: clearcoat.clearcoat_texture, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_normal_channel: clearcoat.clearcoat_normal_channel, - #[cfg(feature = "pbr_multi_layer_material_textures")] - clearcoat_normal_texture: clearcoat.clearcoat_normal_texture, - anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32, - anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32, - #[cfg(feature = "pbr_anisotropy_texture")] - anisotropy_channel: anisotropy.anisotropy_channel, - #[cfg(feature = "pbr_anisotropy_texture")] - anisotropy_texture: anisotropy.anisotropy_texture, - // From the `KHR_materials_specular` spec: - // - reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5, - #[cfg(feature = "pbr_specular_textures")] - specular_channel: specular.specular_channel, - #[cfg(feature = "pbr_specular_textures")] - specular_texture: specular.specular_texture, - specular_tint: match specular.specular_color_factor { - Some(color) => { - Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32) - } - None => Color::WHITE, - }, - #[cfg(feature = "pbr_specular_textures")] - specular_tint_channel: specular.specular_color_channel, - #[cfg(feature = "pbr_specular_textures")] - specular_tint_texture: specular.specular_color_texture, - ..Default::default() - } - }) - .expect("material indices are unique, so the label is unique") + StandardMaterial { + base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]), + base_color_channel, + base_color_texture, + perceptual_roughness: pbr.roughness_factor(), + metallic: pbr.metallic_factor(), + metallic_roughness_channel, + metallic_roughness_texture, + normal_map_channel, + normal_map_texture, + double_sided: material.double_sided(), + cull_mode: if material.double_sided() { + None + } else if is_scale_inverted { + Some(Face::Front) + } else { + Some(Face::Back) + }, + occlusion_channel, + occlusion_texture, + emissive, + emissive_channel, + emissive_texture, + specular_transmission, + #[cfg(feature = "pbr_transmission_textures")] + specular_transmission_channel, + #[cfg(feature = "pbr_transmission_textures")] + specular_transmission_texture, + thickness, + #[cfg(feature = "pbr_transmission_textures")] + thickness_channel, + #[cfg(feature = "pbr_transmission_textures")] + thickness_texture, + ior, + attenuation_distance, + attenuation_color: Color::linear_rgb( + attenuation_color[0], + attenuation_color[1], + attenuation_color[2], + ), + unlit: material.unlit(), + alpha_mode: alpha_mode(material), + uv_transform, + clearcoat: clearcoat.clearcoat_factor.unwrap_or_default() as f32, + clearcoat_perceptual_roughness: clearcoat.clearcoat_roughness_factor.unwrap_or_default() + as f32, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_channel: clearcoat.clearcoat_channel, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_texture: clearcoat.clearcoat_texture, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_normal_channel: clearcoat.clearcoat_normal_channel, + #[cfg(feature = "pbr_multi_layer_material_textures")] + clearcoat_normal_texture: clearcoat.clearcoat_normal_texture, + anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32, + anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32, + #[cfg(feature = "pbr_anisotropy_texture")] + anisotropy_channel: anisotropy.anisotropy_channel, + #[cfg(feature = "pbr_anisotropy_texture")] + anisotropy_texture: anisotropy.anisotropy_texture, + // From the `KHR_materials_specular` spec: + // + reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5, + #[cfg(feature = "pbr_specular_textures")] + specular_channel: specular.specular_channel, + #[cfg(feature = "pbr_specular_textures")] + specular_texture: specular.specular_texture, + specular_tint: match specular.specular_color_factor { + Some(color) => Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32), + None => Color::WHITE, + }, + #[cfg(feature = "pbr_specular_textures")] + specular_tint_channel: specular.specular_color_channel, + #[cfg(feature = "pbr_specular_textures")] + specular_tint_texture: specular.specular_color_texture, + ..Default::default() + } + }) } /// Loads a glTF node. -#[expect( - clippy::result_large_err, - reason = "`GltfError` is only barely past the threshold for large errors." +#[cfg_attr( + not(target_arch = "wasm32"), + expect( + clippy::result_large_err, + reason = "`GltfError` is only barely past the threshold for large errors." + ) )] fn load_node( gltf_node: &Node, @@ -1462,11 +1463,16 @@ fn load_node( }); } - if let Some(name) = material.name() { - mesh_entity.insert(GltfMaterialName(String::from(name))); + if let Some(name) = mesh.name() { + mesh_entity.insert(GltfMeshName(name.to_string())); } - mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive))); + if let Some(name) = material.name() { + mesh_entity.insert(GltfMaterialName(name.to_string())); + } + + mesh_entity.insert(Name::new(primitive_name(&mesh, &material))); + // Mark for adding skinned mesh if let Some(skin) = gltf_node.skin() { entity_to_skin_index_map.insert(mesh_entity.id(), skin.index()); @@ -1690,9 +1696,9 @@ impl ImageOrPath { handles: &mut Vec>, ) { let handle = match self { - ImageOrPath::Image { label, image } => load_context - .add_labeled_asset(label.to_string(), image) - .expect("texture indices are unique, so the label is unique"), + ImageOrPath::Image { label, image } => { + load_context.add_labeled_asset(label.to_string(), image) + } ImageOrPath::Path { path, is_srgb, @@ -1769,7 +1775,8 @@ mod test { }; use bevy_ecs::{resource::Resource, world::World}; use bevy_log::LogPlugin; - use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, MeshPlugin}; + use bevy_mesh::skinning::SkinnedMeshInverseBindposes; + use bevy_render::mesh::MeshPlugin; use bevy_scene::ScenePlugin; fn test_app(dir: Dir) -> App { diff --git a/crates/bevy_gltf/src/vertex_attributes.rs b/crates/bevy_gltf/src/vertex_attributes.rs index 2a9cb2cfab..d4ae811c90 100644 --- a/crates/bevy_gltf/src/vertex_attributes.rs +++ b/crates/bevy_gltf/src/vertex_attributes.rs @@ -1,9 +1,5 @@ -use bevy_platform_support::collections::HashMap; -use bevy_render::{ - mesh::{MeshVertexAttribute, VertexAttributeValues as Values}, - prelude::Mesh, - render_resource::VertexFormat, -}; +use bevy_mesh::{Mesh, MeshVertexAttribute, VertexAttributeValues as Values, VertexFormat}; +use bevy_platform::collections::HashMap; use gltf::{ accessor::{DataType, Dimensions}, mesh::util::{ReadColors, ReadJoints, ReadTexCoords, ReadWeights}, diff --git a/crates/bevy_image/Cargo.toml b/crates/bevy_image/Cargo.toml index ea7c5abee7..10fa026a6b 100644 --- a/crates/bevy_image/Cargo.toml +++ b/crates/bevy_image/Cargo.toml @@ -11,7 +11,9 @@ keywords = ["bevy"] [features] default = ["bevy_reflect"] -bevy_reflect = ["dep:bevy_reflect", "bevy_math/bevy_reflect"] +# bevy_reflect can't optional as it's needed for TypePath +# this feature only control reflection in bevy_image +bevy_reflect = ["bevy_math/bevy_reflect"] # Image formats basis-universal = ["dep:basis-universal"] @@ -30,11 +32,7 @@ qoi = ["image/qoi"] tga = ["image/tga"] tiff = ["image/tiff"] webp = ["image/webp"] -serialize = [ - "bevy_reflect", - "bevy_platform_support/serialize", - "bevy_utils/serde", -] +serialize = ["bevy_reflect", "bevy_platform/serialize"] # For ktx2 supercompression zlib = ["flate2"] @@ -48,10 +46,11 @@ bevy_color = { path = "../bevy_color", version = "0.16.0-dev", features = [ "serialize", "wgpu-types", ] } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } -bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", optional = true } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } @@ -68,7 +67,7 @@ futures-lite = "2.0.1" guillotiere = "0.6.0" rectangle-pack = "0.4" ddsfile = { version = "0.5.2", optional = true } -ktx2 = { version = "0.3.0", optional = true } +ktx2 = { version = "0.4.0", optional = true } # For ktx2 supercompression flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.8.0", optional = true } diff --git a/crates/bevy_image/src/dds.rs b/crates/bevy_image/src/dds.rs index c216ef2844..8dc58ad482 100644 --- a/crates/bevy_image/src/dds.rs +++ b/crates/bevy_image/src/dds.rs @@ -12,7 +12,6 @@ use super::{CompressedImageFormats, Image, TextureError, TranscodeFormat}; #[cfg(feature = "dds")] pub fn dds_buffer_to_image( - #[cfg(debug_assertions)] name: String, buffer: &[u8], supported_compressed_formats: CompressedImageFormats, is_srgb: bool, @@ -65,10 +64,7 @@ pub fn dds_buffer_to_image( let mip_map_level = match dds.get_num_mipmap_levels() { 0 => { #[cfg(debug_assertions)] - once!(warn!( - "Mipmap levels for texture {} are 0, bumping them to 1", - name - )); + once!(warn!("Mipmap levels for texture are 0, bumping them to 1",)); 1 } t => t, @@ -409,7 +405,7 @@ mod test { 0x49, 0x92, 0x24, 0x16, 0x95, 0xae, 0x42, 0xfc, 0, 0xaa, 0x55, 0xff, 0xff, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24, 0xd8, 0xad, 0xae, 0x42, 0xaf, 0x0a, 0xaa, 0x55, ]; - let r = dds_buffer_to_image("".into(), &buffer, CompressedImageFormats::BC, true); + let r = dds_buffer_to_image(&buffer, CompressedImageFormats::BC, true); assert!(r.is_ok()); if let Ok(r) = r { fake_wgpu_create_texture_with_data(&r.texture_descriptor, r.data.as_ref().unwrap()); diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index da2609028f..bbf9283d9e 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -4,16 +4,18 @@ use super::basis::*; use super::dds::*; #[cfg(feature = "ktx2")] use super::ktx2::*; +#[cfg(not(feature = "bevy_reflect"))] +use bevy_reflect::TypePath; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_asset::{Asset, RenderAssetUsages}; use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza}; +use bevy_ecs::resource::Resource; use bevy_math::{AspectRatio, UVec2, UVec3, Vec2}; use core::hash::Hash; use serde::{Deserialize, Serialize}; use thiserror::Error; -use tracing::warn; use wgpu_types::{ AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, @@ -338,13 +340,14 @@ impl ImageFormat { derive(Reflect), reflect(opaque, Default, Debug, Clone) )] +#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))] pub struct Image { /// Raw pixel data. /// If the image is being used as a storage texture which doesn't need to be initialized by the - /// CPU, then this should be `None` - /// Otherwise, it should always be `Some` + /// CPU, then this should be `None`. + /// Otherwise, it should always be `Some`. pub data: Option>, - // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors + // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors. pub texture_descriptor: TextureDescriptor, &'static [TextureFormat]>, /// The [`ImageSampler`] to use during rendering. pub sampler: ImageSampler, @@ -355,7 +358,7 @@ pub struct Image { /// Used in [`Image`], this determines what image sampler to use when rendering. The default setting, /// [`ImageSampler::Default`], will read the sampler from the `ImagePlugin` at setup. /// Setting this to [`ImageSampler::Descriptor`] will override the global default descriptor for this [`Image`]. -#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub enum ImageSampler { /// Default image sampler, derived from the `ImagePlugin` setup. #[default] @@ -400,7 +403,7 @@ impl ImageSampler { /// See [`ImageSamplerDescriptor`] for information how to configure this. /// /// This type mirrors [`AddressMode`]. -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum ImageAddressMode { /// Clamp the value to the edge of the texture. /// @@ -429,7 +432,7 @@ pub enum ImageAddressMode { /// Texel mixing mode when sampling between texels. /// /// This type mirrors [`FilterMode`]. -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum ImageFilterMode { /// Nearest neighbor sampling. /// @@ -445,7 +448,7 @@ pub enum ImageFilterMode { /// Comparison function used for depth and stencil operations. /// /// This type mirrors [`CompareFunction`]. -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ImageCompareFunction { /// Function never passes Never, @@ -472,7 +475,7 @@ pub enum ImageCompareFunction { /// Color variation to use when the sampler addressing mode is [`ImageAddressMode::ClampToBorder`]. /// /// This type mirrors [`SamplerBorderColor`]. -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum ImageSamplerBorderColor { /// RGBA color `[0, 0, 0, 0]`. TransparentBlack, @@ -495,7 +498,7 @@ pub enum ImageSamplerBorderColor { /// a breaking change. /// /// This types mirrors [`SamplerDescriptor`], but that might change in future versions. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImageSamplerDescriptor { pub label: Option, /// How to deal with out of bounds accesses in the u (i.e. x) direction. @@ -847,7 +850,7 @@ impl Image { } /// Resizes the image to the new size, by removing information or appending 0 to the `data`. - /// Does not properly resize the contents of the image, but only its internal `data` buffer. + /// Does not properly scale the contents of the image. pub fn resize(&mut self, size: Extent3d) { self.texture_descriptor.size = size; if let Some(ref mut data) = self.data { @@ -855,8 +858,6 @@ impl Image { size.volume() * self.texture_descriptor.format.pixel_size(), 0, ); - } else { - warn!("Resized an uninitialized image. Directly modify image.texture_descriptor.size instead"); } } @@ -928,16 +929,11 @@ impl Image { /// Load a bytes buffer in a [`Image`], according to type `image_type`, using the `image` /// crate pub fn from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] name: String, buffer: &[u8], image_type: ImageType, - #[expect( - clippy::allow_attributes, - reason = "`unused_variables` may not always lint" - )] - #[allow( - unused_variables, - reason = "`supported_compressed_formats` is needed where the image format is `Basis`, `Dds`, or `Ktx2`; if these are disabled, then `supported_compressed_formats` is unused." + #[cfg_attr( + not(any(feature = "basis-universal", feature = "dds", feature = "ktx2")), + expect(unused_variables, reason = "only used with certain features") )] supported_compressed_formats: CompressedImageFormats, is_srgb: bool, @@ -958,13 +954,7 @@ impl Image { basis_buffer_to_image(buffer, supported_compressed_formats, is_srgb)? } #[cfg(feature = "dds")] - ImageFormat::Dds => dds_buffer_to_image( - #[cfg(debug_assertions)] - name, - buffer, - supported_compressed_formats, - is_srgb, - )?, + ImageFormat::Dds => dds_buffer_to_image(buffer, supported_compressed_formats, is_srgb)?, #[cfg(feature = "ktx2")] ImageFormat::Ktx2 => { ktx2_buffer_to_image(buffer, supported_compressed_formats, is_srgb)? @@ -1659,6 +1649,12 @@ impl CompressedImageFormats { } } +/// For defining which compressed image formats are supported. This will be initialized from available device features +/// in `finish()` of the bevy `RenderPlugin`, but is left for the user to specify if not using the `RenderPlugin`, or +/// the WGPU backend. +#[derive(Resource)] +pub struct CompressedImageFormatSupport(pub CompressedImageFormats); + #[cfg(test)] mod test { use super::*; diff --git a/crates/bevy_image/src/image_loader.rs b/crates/bevy_image/src/image_loader.rs index 2d600fe441..0ef1213b46 100644 --- a/crates/bevy_image/src/image_loader.rs +++ b/crates/bevy_image/src/image_loader.rs @@ -150,8 +150,6 @@ impl AssetLoader for ImageLoader { } }; Ok(Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - load_context.path().display().to_string(), &bytes, image_type, self.supported_compressed_formats, diff --git a/crates/bevy_image/src/ktx2.rs b/crates/bevy_image/src/ktx2.rs index bffea83d10..c86e32ef52 100644 --- a/crates/bevy_image/src/ktx2.rs +++ b/crates/bevy_image/src/ktx2.rs @@ -10,8 +10,8 @@ use bevy_utils::default; #[cfg(any(feature = "flate2", feature = "ruzstd"))] use ktx2::SupercompressionScheme; use ktx2::{ - BasicDataFormatDescriptor, ChannelTypeQualifiers, ColorModel, DataFormatDescriptorHeader, - Header, SampleInformation, + ChannelTypeQualifiers, ColorModel, DfdBlockBasic, DfdBlockHeaderBasic, DfdHeader, Header, + SampleInformation, }; use wgpu_types::{ AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, @@ -45,28 +45,28 @@ pub fn ktx2_buffer_to_image( // Handle supercompression let mut levels = Vec::new(); if let Some(supercompression_scheme) = supercompression_scheme { - for (_level, _level_data) in ktx2.levels().enumerate() { + for (level_index, level) in ktx2.levels().enumerate() { match supercompression_scheme { #[cfg(feature = "flate2")] SupercompressionScheme::ZLIB => { - let mut decoder = flate2::bufread::ZlibDecoder::new(_level_data); + let mut decoder = flate2::bufread::ZlibDecoder::new(level.data); let mut decompressed = Vec::new(); decoder.read_to_end(&mut decompressed).map_err(|err| { TextureError::SuperDecompressionError(format!( - "Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}", + "Failed to decompress {supercompression_scheme:?} for mip {level_index}: {err:?}", )) })?; levels.push(decompressed); } #[cfg(feature = "ruzstd")] SupercompressionScheme::Zstandard => { - let mut cursor = std::io::Cursor::new(_level_data); + let mut cursor = std::io::Cursor::new(level.data); let mut decoder = ruzstd::decoding::StreamingDecoder::new(&mut cursor) .map_err(|err| TextureError::SuperDecompressionError(err.to_string()))?; let mut decompressed = Vec::new(); decoder.read_to_end(&mut decompressed).map_err(|err| { TextureError::SuperDecompressionError(format!( - "Failed to decompress {supercompression_scheme:?} for mip {_level}: {err:?}", + "Failed to decompress {supercompression_scheme:?} for mip {level_index}: {err:?}", )) })?; levels.push(decompressed); @@ -79,7 +79,7 @@ pub fn ktx2_buffer_to_image( } } } else { - levels = ktx2.levels().map(<[u8]>::to_vec).collect(); + levels = ktx2.levels().map(|level| level.data.to_vec()).collect(); } // Identify the format @@ -267,6 +267,9 @@ pub fn ktx2_buffer_to_image( let mut image = Image::default(); image.texture_descriptor.format = texture_format; image.data = Some(wgpu_data.into_iter().flatten().collect::>()); + // Note: we must give wgpu the logical texture dimensions, so it can correctly compute mip sizes. + // However this currently causes wgpu to panic if the dimensions arent a multiple of blocksize. + // See https://github.com/gfx-rs/wgpu/issues/7677 for more context. image.texture_descriptor.size = Extent3d { width, height, @@ -276,8 +279,7 @@ pub fn ktx2_buffer_to_image( depth } .max(1), - } - .physical_size(texture_format); + }; image.texture_descriptor.mip_level_count = level_count; image.texture_descriptor.dimension = if depth > 1 { TextureDimension::D3 @@ -397,16 +399,15 @@ pub fn ktx2_get_texture_format>( return ktx2_format_to_texture_format(format, is_srgb); } - for data_format_descriptor in ktx2.data_format_descriptors() { - if data_format_descriptor.header == DataFormatDescriptorHeader::BASIC { - let basic_data_format_descriptor = - BasicDataFormatDescriptor::parse(data_format_descriptor.data) - .map_err(|err| TextureError::InvalidData(format!("KTX2: {err:?}")))?; + for data_format_descriptor in ktx2.dfd_blocks() { + if data_format_descriptor.header == DfdHeader::BASIC { + let basic_data_format_descriptor = DfdBlockBasic::parse(data_format_descriptor.data) + .map_err(|err| TextureError::InvalidData(format!("KTX2: {err:?}")))?; let sample_information = basic_data_format_descriptor .sample_information() .collect::>(); - return ktx2_dfd_to_texture_format( - &basic_data_format_descriptor, + return ktx2_dfd_header_to_texture_format( + &basic_data_format_descriptor.header, &sample_information, is_srgb, ); @@ -476,8 +477,8 @@ fn sample_information_to_data_type( } #[cfg(feature = "ktx2")] -pub fn ktx2_dfd_to_texture_format( - data_format_descriptor: &BasicDataFormatDescriptor, +pub fn ktx2_dfd_header_to_texture_format( + data_format_descriptor: &DfdBlockHeaderBasic, sample_information: &[SampleInformation], is_srgb: bool, ) -> Result { @@ -495,7 +496,7 @@ pub fn ktx2_dfd_to_texture_format( let sample = &sample_information[0]; let data_type = sample_information_to_data_type(sample, false)?; - match sample.bit_length { + match sample.bit_length.get() { 8 => match data_type { DataType::Unorm => TextureFormat::R8Unorm, DataType::UnormSrgb => { @@ -577,7 +578,7 @@ pub fn ktx2_dfd_to_texture_format( let sample = &sample_information[0]; let data_type = sample_information_to_data_type(sample, false)?; - match sample.bit_length { + match sample.bit_length.get() { 8 => match data_type { DataType::Unorm => TextureFormat::Rg8Unorm, DataType::UnormSrgb => { @@ -635,27 +636,27 @@ pub fn ktx2_dfd_to_texture_format( } 3 => { if sample_information[0].channel_type == 0 - && sample_information[0].bit_length == 11 + && sample_information[0].bit_length.get() == 11 && sample_information[1].channel_type == 1 - && sample_information[1].bit_length == 11 + && sample_information[1].bit_length.get() == 11 && sample_information[2].channel_type == 2 - && sample_information[2].bit_length == 10 + && sample_information[2].bit_length.get() == 10 { TextureFormat::Rg11b10Ufloat } else if sample_information[0].channel_type == 0 - && sample_information[0].bit_length == 9 + && sample_information[0].bit_length.get() == 9 && sample_information[1].channel_type == 1 - && sample_information[1].bit_length == 9 + && sample_information[1].bit_length.get() == 9 && sample_information[2].channel_type == 2 - && sample_information[2].bit_length == 9 + && sample_information[2].bit_length.get() == 9 { TextureFormat::Rgb9e5Ufloat } else if sample_information[0].channel_type == 0 - && sample_information[0].bit_length == 8 + && sample_information[0].bit_length.get() == 8 && sample_information[1].channel_type == 1 - && sample_information[1].bit_length == 8 + && sample_information[1].bit_length.get() == 8 && sample_information[2].channel_type == 2 - && sample_information[2].bit_length == 8 + && sample_information[2].bit_length.get() == 8 { return Err(TextureError::FormatRequiresTranscodingError( TranscodeFormat::Rgb8, @@ -681,10 +682,10 @@ pub fn ktx2_dfd_to_texture_format( assert_eq!(sample_information[3].channel_type, 15); // Handle one special packed format - if sample_information[0].bit_length == 10 - && sample_information[1].bit_length == 10 - && sample_information[2].bit_length == 10 - && sample_information[3].bit_length == 2 + if sample_information[0].bit_length.get() == 10 + && sample_information[1].bit_length.get() == 10 + && sample_information[2].bit_length.get() == 10 + && sample_information[3].bit_length.get() == 2 { return Ok(TextureFormat::Rgb10a2Unorm); } @@ -708,7 +709,7 @@ pub fn ktx2_dfd_to_texture_format( let sample = &sample_information[0]; let data_type = sample_information_to_data_type(sample, is_srgb)?; - match sample.bit_length { + match sample.bit_length.get() { 8 => match data_type { DataType::Unorm => { if is_rgba { @@ -896,7 +897,7 @@ pub fn ktx2_dfd_to_texture_format( Some(ColorModel::XYZW) => { // Same number of channels in both texel block dimensions and sample info descriptions assert_eq!( - data_format_descriptor.texel_block_dimensions[0] as usize, + data_format_descriptor.texel_block_dimensions[0].get() as usize, sample_information.len() ); match sample_information.len() { @@ -935,7 +936,7 @@ pub fn ktx2_dfd_to_texture_format( let sample = &sample_information[0]; let data_type = sample_information_to_data_type(sample, false)?; - match sample.bit_length { + match sample.bit_length.get() { 8 => match data_type { DataType::Unorm => TextureFormat::Rgba8Unorm, DataType::UnormSrgb => { @@ -1124,8 +1125,8 @@ pub fn ktx2_dfd_to_texture_format( }, Some(ColorModel::ASTC) => TextureFormat::Astc { block: match ( - data_format_descriptor.texel_block_dimensions[0], - data_format_descriptor.texel_block_dimensions[1], + data_format_descriptor.texel_block_dimensions[0].get(), + data_format_descriptor.texel_block_dimensions[1].get(), ) { (4, 4) => AstcBlock::B4x4, (5, 4) => AstcBlock::B5x4, diff --git a/crates/bevy_image/src/texture_atlas.rs b/crates/bevy_image/src/texture_atlas.rs index feaa8fc96c..4caeed8c07 100644 --- a/crates/bevy_image/src/texture_atlas.rs +++ b/crates/bevy_image/src/texture_atlas.rs @@ -1,7 +1,9 @@ use bevy_app::prelude::*; use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle}; use bevy_math::{Rect, URect, UVec2}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; +#[cfg(not(feature = "bevy_reflect"))] +use bevy_reflect::TypePath; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(feature = "serialize")] @@ -97,6 +99,7 @@ impl TextureAtlasSources { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] +#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))] pub struct TextureAtlasLayout { /// Total size of texture atlas. pub size: UVec2, @@ -219,6 +222,18 @@ impl TextureAtlas { let atlas = texture_atlases.get(&self.layout)?; atlas.textures.get(self.index).copied() } + + /// Returns this [`TextureAtlas`] with the specified index. + pub fn with_index(mut self, index: usize) -> Self { + self.index = index; + self + } + + /// Returns this [`TextureAtlas`] with the specified [`TextureAtlasLayout`] handle. + pub fn with_layout(mut self, layout: Handle) -> Self { + self.layout = layout; + self + } } impl From> for TextureAtlas { diff --git a/crates/bevy_image/src/texture_atlas_builder.rs b/crates/bevy_image/src/texture_atlas_builder.rs index b7fd2cf168..2f23331c8c 100644 --- a/crates/bevy_image/src/texture_atlas_builder.rs +++ b/crates/bevy_image/src/texture_atlas_builder.rs @@ -1,6 +1,6 @@ use bevy_asset::{AssetId, RenderAssetUsages}; use bevy_math::{URect, UVec2}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use rectangle_pack::{ contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation, RectToInsert, TargetBin, diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 602d1be8ea..f6752abb05 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -24,10 +24,10 @@ bevy_reflect = [ ## Adds serialization support through `serde`. serialize = [ "serde", - "smol_str/serde", + "smol_str?/serde", "bevy_ecs/serialize", "bevy_math/serialize", - "bevy_platform_support/serialize", + "bevy_platform/serialize", ] ## Uses the small-string optimization provided by `smol_str`. @@ -42,9 +42,8 @@ std = [ "bevy_app/std", "bevy_ecs/std", "bevy_math/std", - "bevy_utils/std", "bevy_reflect/std", - "bevy_platform_support/std", + "bevy_platform/std", ] ## `critical-section` provides the building blocks for synchronization primitives @@ -53,7 +52,7 @@ critical-section = [ "bevy_app/critical-section", "bevy_ecs/critical-section", "bevy_reflect?/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", ] ## Uses the `libm` maths library instead of the one provided in `std` and `core`. @@ -68,7 +67,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "glam", ], default-features = false, optional = true } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } # other serde = { version = "1", features = [ diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index acdf0135f0..99909762c7 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -1,7 +1,7 @@ //! The generic axis type. use bevy_ecs::resource::Resource; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::hash::Hash; #[cfg(feature = "bevy_reflect")] diff --git a/crates/bevy_input/src/button_input.rs b/crates/bevy_input/src/button_input.rs index 7f2095dd51..58ab62aefa 100644 --- a/crates/bevy_input/src/button_input.rs +++ b/crates/bevy_input/src/button_input.rs @@ -1,7 +1,7 @@ //! The generic input type. use bevy_ecs::resource::Resource; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use core::hash::Hash; #[cfg(feature = "bevy_reflect")] use { @@ -71,7 +71,7 @@ use { /// Reading and checking against the current set of pressed buttons: /// ```no_run /// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, Update}; -/// # use bevy_ecs::{prelude::{IntoScheduleConfigs, Res, Resource, resource_changed}, schedule::Condition}; +/// # use bevy_ecs::{prelude::{IntoScheduleConfigs, Res, Resource, resource_changed}, schedule::SystemCondition}; /// # use bevy_input::{ButtonInput, prelude::{KeyCode, MouseButton}}; /// /// fn main() { diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 3a3e2c7cb7..2b0148909c 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -16,7 +16,7 @@ use bevy_ecs::{ }; use bevy_math::ops; use bevy_math::Vec2; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index e1119c3d35..67c8995179 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -76,7 +76,11 @@ pub struct InputPlugin; /// Label for systems that update the input data. #[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)] -pub struct InputSystem; +pub struct InputSystems; + +/// Deprecated alias for [`InputSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `InputSystems`.")] +pub type InputSystem = InputSystems; impl Plugin for InputPlugin { fn build(&self, app: &mut App) { @@ -85,7 +89,7 @@ impl Plugin for InputPlugin { .add_event::() .add_event::() .init_resource::>() - .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem)) + .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystems)) // mouse .add_event::() .add_event::() @@ -98,7 +102,7 @@ impl Plugin for InputPlugin { accumulate_mouse_motion_system, accumulate_mouse_scroll_system, ) - .in_set(InputSystem), + .in_set(InputSystems), ) .add_event::() .add_event::() @@ -122,12 +126,12 @@ impl Plugin for InputPlugin { gamepad_connection_system, gamepad_event_processing_system.after(gamepad_connection_system), ) - .in_set(InputSystem), + .in_set(InputSystems), ) // touch .add_event::() .init_resource::() - .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystem)); + .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystems)); #[cfg(feature = "bevy_reflect")] { diff --git a/crates/bevy_input/src/touch.rs b/crates/bevy_input/src/touch.rs index 3339e02f2e..28f3159d53 100644 --- a/crates/bevy_input/src/touch.rs +++ b/crates/bevy_input/src/touch.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ system::ResMut, }; use bevy_math::Vec2; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; diff --git a/crates/bevy_input_focus/src/directional_navigation.rs b/crates/bevy_input_focus/src/directional_navigation.rs index f02e523eb3..2f3d647025 100644 --- a/crates/bevy_input_focus/src/directional_navigation.rs +++ b/crates/bevy_input_focus/src/directional_navigation.rs @@ -17,7 +17,7 @@ use bevy_app::prelude::*; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, + entity::{EntityHashMap, EntityHashSet}, prelude::*, system::SystemParam, }; diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 2697efc0c8..44ff0ef645 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -136,7 +136,7 @@ pub struct InputFocusVisible(pub bool); /// If no entity has input focus, then the event is dispatched to the main window. /// /// To set up your own bubbling input event, add the [`dispatch_focused_input::`](dispatch_focused_input) system to your app, -/// in the [`InputFocusSet::Dispatch`] system set during [`PreUpdate`]. +/// in the [`InputFocusSystems::Dispatch`] system set during [`PreUpdate`]. #[derive(Clone, Debug, Component)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))] pub struct FocusedInput { @@ -165,7 +165,7 @@ impl Traversal> for WindowTraversal { // Send event to parent, if it has one. if let Some(child_of) = child_of { - return Some(child_of.parent); + return Some(child_of.parent()); }; // Otherwise, send it to the window entity (unless this is a window entity). @@ -195,7 +195,7 @@ impl Plugin for InputDispatchPlugin { dispatch_focused_input::, dispatch_focused_input::, ) - .in_set(InputFocusSet::Dispatch), + .in_set(InputFocusSystems::Dispatch), ); #[cfg(feature = "bevy_reflect")] @@ -209,11 +209,15 @@ impl Plugin for InputDispatchPlugin { /// /// These systems run in the [`PreUpdate`] schedule. #[derive(SystemSet, Debug, PartialEq, Eq, Hash, Clone)] -pub enum InputFocusSet { +pub enum InputFocusSystems { /// System which dispatches bubbled input events to the focused entity, or to the primary window. Dispatch, } +/// Deprecated alias for [`InputFocusSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `InputFocusSystems`.")] +pub type InputFocusSet = InputFocusSystems; + /// Sets the initial focus to the primary window, if any. pub fn set_initial_focus( mut input_focus: ResMut, @@ -338,7 +342,7 @@ impl IsFocused for World { if e == entity { return true; } - if let Some(parent) = self.entity(e).get::().map(|c| c.parent) { + if let Some(parent) = self.entity(e).get::().map(ChildOf::parent) { e = parent; } else { return false; diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 2d44f7c330..60df130ae0 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -221,7 +221,7 @@ impl TabNavigation<'_, '_> { action: NavAction, ) -> Result { // List of all focusable entities found. - let mut focusable: Vec<(Entity, TabIndex)> = + let mut focusable: Vec<(Entity, TabIndex, usize)> = Vec::with_capacity(self.tabindex_query.iter().len()); match tabgroup { @@ -229,7 +229,7 @@ impl TabNavigation<'_, '_> { // We're in a modal tab group, then gather all tab indices in that group. if let Ok((_, _, children)) = self.tabgroup_query.get(tg_entity) { for child in children.iter() { - self.gather_focusable(&mut focusable, *child); + self.gather_focusable(&mut focusable, *child, 0); } } } @@ -245,9 +245,12 @@ impl TabNavigation<'_, '_> { tab_groups.sort_by_key(|(_, tg)| tg.order); // Search group descendants - tab_groups.iter().for_each(|(tg_entity, _)| { - self.gather_focusable(&mut focusable, *tg_entity); - }); + tab_groups + .iter() + .enumerate() + .for_each(|(idx, (tg_entity, _))| { + self.gather_focusable(&mut focusable, *tg_entity, idx); + }); } } @@ -255,8 +258,14 @@ impl TabNavigation<'_, '_> { return Err(TabNavigationError::NoFocusableEntities); } - // Stable sort by tabindex - focusable.sort_by_key(|(_, idx)| *idx); + // Sort by TabGroup and then TabIndex + focusable.sort_by(|(_, a_tab_idx, a_group), (_, b_tab_idx, b_group)| { + if a_group == b_group { + a_tab_idx.cmp(b_tab_idx) + } else { + a_group.cmp(b_group) + } + }); let index = focusable.iter().position(|e| Some(e.0) == focus.0); let count = focusable.len(); @@ -267,31 +276,36 @@ impl TabNavigation<'_, '_> { (None, NavAction::Previous) | (_, NavAction::Last) => count - 1, }; match focusable.get(next) { - Some((entity, _)) => Ok(*entity), + Some((entity, _, _)) => Ok(*entity), None => Err(TabNavigationError::FailedToNavigateToNextFocusableEntity), } } /// Gather all focusable entities in tree order. - fn gather_focusable(&self, out: &mut Vec<(Entity, TabIndex)>, parent: Entity) { + fn gather_focusable( + &self, + out: &mut Vec<(Entity, TabIndex, usize)>, + parent: Entity, + tab_group_idx: usize, + ) { if let Ok((entity, tabindex, children)) = self.tabindex_query.get(parent) { if let Some(tabindex) = tabindex { if tabindex.0 >= 0 { - out.push((entity, *tabindex)); + out.push((entity, *tabindex, tab_group_idx)); } } if let Some(children) = children { for child in children.iter() { // Don't traverse into tab groups, as they are handled separately. if self.tabgroup_query.get(*child).is_err() { - self.gather_focusable(out, *child); + self.gather_focusable(out, *child, tab_group_idx); } } } } else if let Ok((_, tabgroup, children)) = self.tabgroup_query.get(parent) { if !tabgroup.modal { for child in children.iter() { - self.gather_focusable(out, *child); + self.gather_focusable(out, *child, tab_group_idx); } } } @@ -375,22 +389,8 @@ mod tests { let world = app.world_mut(); let tab_group_entity = world.spawn(TabGroup::new(0)).id(); - let tab_entity_1 = world - .spawn(( - TabIndex(0), - ChildOf { - parent: tab_group_entity, - }, - )) - .id(); - let tab_entity_2 = world - .spawn(( - TabIndex(1), - ChildOf { - parent: tab_group_entity, - }, - )) - .id(); + let tab_entity_1 = world.spawn((TabIndex(0), ChildOf(tab_group_entity))).id(); + let tab_entity_2 = world.spawn((TabIndex(1), ChildOf(tab_group_entity))).id(); let mut system_state: SystemState = SystemState::new(world); let tab_navigation = system_state.get(world); @@ -411,4 +411,45 @@ mod tests { let last_entity = tab_navigation.navigate(&InputFocus::default(), NavAction::Last); assert_eq!(last_entity, Ok(tab_entity_2)); } + + #[test] + fn test_tab_navigation_between_groups_is_sorted_by_group() { + let mut app = App::new(); + let world = app.world_mut(); + + let tab_group_1 = world.spawn(TabGroup::new(0)).id(); + let tab_entity_1 = world.spawn((TabIndex(0), ChildOf(tab_group_1))).id(); + let tab_entity_2 = world.spawn((TabIndex(1), ChildOf(tab_group_1))).id(); + + let tab_group_2 = world.spawn(TabGroup::new(1)).id(); + let tab_entity_3 = world.spawn((TabIndex(0), ChildOf(tab_group_2))).id(); + let tab_entity_4 = world.spawn((TabIndex(1), ChildOf(tab_group_2))).id(); + + let mut system_state: SystemState = SystemState::new(world); + let tab_navigation = system_state.get(world); + assert_eq!(tab_navigation.tabgroup_query.iter().count(), 2); + assert_eq!(tab_navigation.tabindex_query.iter().count(), 4); + + let next_entity = + tab_navigation.navigate(&InputFocus::from_entity(tab_entity_1), NavAction::Next); + assert_eq!(next_entity, Ok(tab_entity_2)); + + let prev_entity = + tab_navigation.navigate(&InputFocus::from_entity(tab_entity_2), NavAction::Previous); + assert_eq!(prev_entity, Ok(tab_entity_1)); + + let first_entity = tab_navigation.navigate(&InputFocus::default(), NavAction::First); + assert_eq!(first_entity, Ok(tab_entity_1)); + + let last_entity = tab_navigation.navigate(&InputFocus::default(), NavAction::Last); + assert_eq!(last_entity, Ok(tab_entity_4)); + + let next_from_end_of_group_entity = + tab_navigation.navigate(&InputFocus::from_entity(tab_entity_2), NavAction::Next); + assert_eq!(next_from_end_of_group_entity, Ok(tab_entity_3)); + + let prev_entity_from_start_of_group = + tab_navigation.navigate(&InputFocus::from_entity(tab_entity_3), NavAction::Previous); + assert_eq!(prev_entity_from_start_of_group, Ok(tab_entity_2)); + } } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index b0be2f00c8..819b8dab8e 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -14,6 +14,7 @@ trace = [ "bevy_app/trace", "bevy_asset?/trace", "bevy_core_pipeline?/trace", + "bevy_anti_aliasing?/trace", "bevy_ecs/trace", "bevy_log/trace", "bevy_pbr?/trace", @@ -29,12 +30,6 @@ sysinfo_plugin = ["bevy_diagnostic/sysinfo_plugin"] # Texture formats that have specific rendering support (HDR enabled by default) basis-universal = ["bevy_image/basis-universal", "bevy_render/basis-universal"] -dds = [ - "bevy_image/dds", - "bevy_render/dds", - "bevy_core_pipeline/dds", - "bevy_gltf?/dds", -] exr = ["bevy_image/exr", "bevy_render/exr"] hdr = ["bevy_image/hdr", "bevy_render/hdr"] ktx2 = ["bevy_image/ktx2", "bevy_render/ktx2"] @@ -55,6 +50,7 @@ qoi = ["bevy_image/qoi"] tga = ["bevy_image/tga"] tiff = ["bevy_image/tiff"] webp = ["bevy_image/webp"] +dds = ["bevy_image/dds"] # Enable SPIR-V passthrough spirv_shader_passthrough = ["bevy_render/spirv_shader_passthrough"] @@ -67,7 +63,7 @@ statically-linked-dxc = ["bevy_render/statically-linked-dxc"] tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"] # Include SMAA LUT KTX2 Files -smaa_luts = ["bevy_core_pipeline/smaa_luts"] +smaa_luts = ["bevy_anti_aliasing/smaa_luts"] # Audio format support (vorbis is enabled by default) flac = ["bevy_audio/flac"] @@ -103,7 +99,7 @@ serialize = [ "bevy_ui?/serialize", "bevy_window?/serialize", "bevy_winit?/serialize", - "bevy_platform_support/serialize", + "bevy_platform/serialize", ] multi_threaded = [ "std", @@ -152,6 +148,7 @@ pbr_specular_textures = [ # Optimise for WebGL2 webgl = [ "bevy_core_pipeline?/webgl", + "bevy_anti_aliasing?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl", @@ -160,6 +157,7 @@ webgl = [ webgpu = [ "bevy_core_pipeline?/webgpu", + "bevy_anti_aliasing?/webgpu", "bevy_pbr?/webgpu", "bevy_render?/webgpu", "bevy_gizmos?/webgpu", @@ -176,6 +174,7 @@ bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos?/bevy_sprite", "bevy_image"] bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr", "bevy_image"] bevy_window = ["dep:bevy_window", "dep:bevy_a11y"] bevy_core_pipeline = ["dep:bevy_core_pipeline", "bevy_image"] +bevy_anti_aliasing = ["dep:bevy_anti_aliasing", "bevy_image"] bevy_gizmos = ["dep:bevy_gizmos", "bevy_image"] bevy_gltf = ["dep:bevy_gltf", "bevy_image"] bevy_ui = ["dep:bevy_ui", "bevy_image"] @@ -292,12 +291,11 @@ std = [ "bevy_input/std", "bevy_input_focus?/std", "bevy_math/std", - "bevy_platform_support/std", + "bevy_platform/std", "bevy_reflect/std", "bevy_state?/std", "bevy_time/std", "bevy_transform/std", - "bevy_utils/std", "bevy_tasks/std", "bevy_window?/std", ] @@ -311,17 +309,15 @@ critical-section = [ "bevy_ecs/critical-section", "bevy_input/critical-section", "bevy_input_focus?/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", "bevy_reflect/critical-section", "bevy_state?/critical-section", "bevy_time/critical-section", - "bevy_utils/critical-section", "bevy_tasks/critical-section", ] # Uses the `libm` maths library instead of the one provided in `std` and `core`. libm = [ - "bevy_a11y?/libm", "bevy_color?/libm", "bevy_input/libm", "bevy_input_focus?/libm", @@ -343,7 +339,7 @@ async_executor = [ # Note this is currently only applicable on `wasm32` architectures. web = [ "bevy_app/web", - "bevy_platform_support/web", + "bevy_platform/web", "bevy_reflect/web", "bevy_tasks/web", ] @@ -360,11 +356,12 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = fa ] } bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false, features = [ "bevy_reflect", -], optional = true } +] } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [ "bevy_reflect", -], optional = true } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ + "nostd-libm", +] } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "alloc", ] } bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev", default-features = false } @@ -377,10 +374,8 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev", default-features = false, features = [ "bevy-support", "bevy_reflect", -], optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", ] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false } # bevy (std required) @@ -398,6 +393,7 @@ bevy_color = { path = "../bevy_color", optional = true, version = "0.16.0-dev", "bevy_reflect", ] } bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.16.0-dev" } +bevy_anti_aliasing = { path = "../bevy_anti_aliasing", optional = true, version = "0.16.0-dev" } bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.16.0-dev" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.16.0-dev" } bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.16.0-dev", default-features = false } @@ -421,7 +417,7 @@ bevy_ui = { path = "../bevy_ui", optional = true, version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev", default-features = false, features = [ "bevy_reflect", ] } -bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev" } +bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev", default-features = false } [lints] workspace = true diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 259a8c8922..db1152a362 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -9,10 +9,8 @@ plugin_group! { bevy_app:::TaskPoolPlugin, bevy_diagnostic:::FrameCountPlugin, bevy_time:::TimePlugin, - #[custom(cfg(any(feature = "libm", feature = "std")))] bevy_transform:::TransformPlugin, bevy_diagnostic:::DiagnosticsPlugin, - #[custom(cfg(any(feature = "libm", feature = "std")))] bevy_input:::InputPlugin, #[custom(cfg(not(feature = "bevy_window")))] bevy_app:::ScheduleRunnerPlugin, @@ -40,6 +38,8 @@ plugin_group! { bevy_render::pipelined_rendering:::PipelinedRenderingPlugin, #[cfg(feature = "bevy_core_pipeline")] bevy_core_pipeline:::CorePipelinePlugin, + #[cfg(feature = "bevy_anti_aliasing")] + bevy_anti_aliasing:::AntiAliasingPlugin, #[cfg(feature = "bevy_sprite")] bevy_sprite:::SpritePlugin, #[cfg(feature = "bevy_text")] diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 37bcef26b2..07dd936ab1 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -18,6 +18,8 @@ pub use default_plugins::*; pub use bevy_a11y as a11y; #[cfg(feature = "bevy_animation")] pub use bevy_animation as animation; +#[cfg(feature = "bevy_anti_aliasing")] +pub use bevy_anti_aliasing as anti_aliasing; pub use bevy_app as app; #[cfg(feature = "bevy_asset")] pub use bevy_asset as asset; @@ -39,19 +41,17 @@ pub use bevy_gizmos as gizmos; pub use bevy_gltf as gltf; #[cfg(feature = "bevy_image")] pub use bevy_image as image; -#[cfg(any(feature = "libm", feature = "std"))] pub use bevy_input as input; #[cfg(feature = "bevy_input_focus")] pub use bevy_input_focus as input_focus; #[cfg(feature = "bevy_log")] pub use bevy_log as log; -#[cfg(any(feature = "libm", feature = "std"))] pub use bevy_math as math; #[cfg(feature = "bevy_pbr")] pub use bevy_pbr as pbr; #[cfg(feature = "bevy_picking")] pub use bevy_picking as picking; -pub use bevy_platform_support as platform_support; +pub use bevy_platform as platform; pub use bevy_ptr as ptr; pub use bevy_reflect as reflect; #[cfg(feature = "bevy_remote")] @@ -68,7 +68,6 @@ pub use bevy_tasks as tasks; #[cfg(feature = "bevy_text")] pub use bevy_text as text; pub use bevy_time as time; -#[cfg(any(feature = "libm", feature = "std"))] pub use bevy_transform as transform; #[cfg(feature = "bevy_ui")] pub use bevy_ui as ui; diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index a01cc4da2c..26d5c7e2af 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -1,13 +1,10 @@ #[doc(hidden)] pub use crate::{ - app::prelude::*, ecs::prelude::*, platform_support::prelude::*, reflect::prelude::*, - time::prelude::*, utils::prelude::*, DefaultPlugins, MinimalPlugins, + app::prelude::*, ecs::prelude::*, input::prelude::*, math::prelude::*, platform::prelude::*, + reflect::prelude::*, time::prelude::*, transform::prelude::*, utils::prelude::*, + DefaultPlugins, MinimalPlugins, }; -#[doc(hidden)] -#[cfg(any(feature = "libm", feature = "std"))] -pub use crate::{input::prelude::*, math::prelude::*, transform::prelude::*}; - #[doc(hidden)] #[cfg(feature = "bevy_log")] pub use crate::log::prelude::*; diff --git a/crates/bevy_log/Cargo.toml b/crates/bevy_log/Cargo.toml index cc7c53e676..32902a2dda 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -16,6 +16,7 @@ trace_tracy_memory = ["dep:tracy-client"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } # other diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 055395bad7..aa8092c834 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -56,6 +56,7 @@ use bevy_app::{App, Plugin}; use tracing_log::LogTracer; use tracing_subscriber::{ filter::{FromEnvError, ParseError}, + layer::Layered, prelude::*, registry::Registry, EnvFilter, Layer, @@ -63,7 +64,7 @@ use tracing_subscriber::{ #[cfg(feature = "tracing-chrome")] use { bevy_ecs::resource::Resource, - bevy_utils::synccell::SyncCell, + bevy_platform::cell::SyncCell, tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}, }; @@ -97,6 +98,7 @@ pub(crate) struct FlushGuard(SyncCell); /// level: Level::DEBUG, /// filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(), /// custom_layer: |_| None, +/// fmt_layer: |_| None, /// })) /// .run(); /// } @@ -240,11 +242,38 @@ pub struct LogPlugin { /// /// Please see the `examples/log_layers.rs` for a complete example. pub custom_layer: fn(app: &mut App) -> Option, + + /// Override the default [`tracing_subscriber::fmt::Layer`] with a custom one. + /// + /// This differs from [`custom_layer`](Self::custom_layer) in that + /// [`fmt_layer`](Self::fmt_layer) allows you to overwrite the default formatter layer, while + /// `custom_layer` only allows you to add additional layers (which are unable to modify the + /// default formatter). + /// + /// For example, you can use [`tracing_subscriber::fmt::Layer::without_time`] to remove the + /// timestamp from the log output. + /// + /// Please see the `examples/log_layers.rs` for a complete example. + pub fmt_layer: fn(app: &mut App) -> Option, } -/// A boxed [`Layer`] that can be used with [`LogPlugin`]. +/// A boxed [`Layer`] that can be used with [`LogPlugin::custom_layer`]. pub type BoxedLayer = Box + Send + Sync + 'static>; +#[cfg(feature = "trace")] +type BaseSubscriber = + Layered + Send + Sync>>, Registry>>; + +#[cfg(feature = "trace")] +type PreFmtSubscriber = Layered, BaseSubscriber>; + +#[cfg(not(feature = "trace"))] +type PreFmtSubscriber = + Layered + Send + Sync>>, Registry>>; + +/// A boxed [`Layer`] that can be used with [`LogPlugin::fmt_layer`]. +pub type BoxedFmtLayer = Box + Send + Sync + 'static>; + /// The default [`LogPlugin`] [`EnvFilter`]. pub const DEFAULT_FILTER: &str = "wgpu=error,naga=warn"; @@ -254,6 +283,7 @@ impl Default for LogPlugin { filter: DEFAULT_FILTER.to_string(), level: Level::INFO, custom_layer: |_| None, + fmt_layer: |_| None, } } } @@ -328,10 +358,12 @@ impl Plugin for LogPlugin { #[cfg(feature = "tracing-tracy")] let tracy_layer = tracing_tracy::TracyLayer::default(); - // note: the implementation of `Default` reads from the env var NO_COLOR - // to decide whether to use ANSI color codes, which is common convention - // https://no-color.org/ - let fmt_layer = tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr); + let fmt_layer = (self.fmt_layer)(app).unwrap_or_else(|| { + // note: the implementation of `Default` reads from the env var NO_COLOR + // to decide whether to use ANSI color codes, which is common convention + // https://no-color.org/ + Box::new(tracing_subscriber::fmt::Layer::default().with_writer(std::io::stderr)) + }); // bevy_render::renderer logs a `tracy.frame_mark` event every frame // at Level::INFO. Formatted logs should omit it. diff --git a/crates/bevy_macro_utils/src/bevy_manifest.rs b/crates/bevy_macro_utils/src/bevy_manifest.rs index b0d321ba22..b6df0e0e0f 100644 --- a/crates/bevy_macro_utils/src/bevy_manifest.rs +++ b/crates/bevy_macro_utils/src/bevy_manifest.rs @@ -95,7 +95,7 @@ impl BevyManifest { return None; }; - let mut path = Self::parse_str::(package); + let mut path = Self::parse_str::(&format!("::{package}")); if let Some(module) = name.strip_prefix("bevy_") { path.segments.push(Self::parse_str(module)); } diff --git a/crates/bevy_macro_utils/src/label.rs b/crates/bevy_macro_utils/src/label.rs index 1fc540c9c4..7669f85f1a 100644 --- a/crates/bevy_macro_utils/src/label.rs +++ b/crates/bevy_macro_utils/src/label.rs @@ -58,7 +58,6 @@ pub fn derive_label( input: syn::DeriveInput, trait_name: &str, trait_path: &syn::Path, - dyn_eq_path: &syn::Path, ) -> TokenStream { if let syn::Data::Union(_) = &input.data { let message = format!("Cannot derive {trait_name} for unions."); @@ -89,16 +88,6 @@ pub fn derive_label( fn dyn_clone(&self) -> alloc::boxed::Box { alloc::boxed::Box::new(::core::clone::Clone::clone(self)) } - - fn as_dyn_eq(&self) -> &dyn #dyn_eq_path { - self - } - - fn dyn_hash(&self, mut state: &mut dyn ::core::hash::Hasher) { - let ty_id = ::core::any::TypeId::of::(); - ::core::hash::Hash::hash(&ty_id, &mut state); - ::core::hash::Hash::hash(self, &mut state); - } } }; } diff --git a/crates/bevy_macro_utils/src/shape.rs b/crates/bevy_macro_utils/src/shape.rs index 2d0124d62a..502738cb9b 100644 --- a/crates/bevy_macro_utils/src/shape.rs +++ b/crates/bevy_macro_utils/src/shape.rs @@ -1,24 +1,29 @@ -use proc_macro::Span; -use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Error, Field, Fields}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataEnum, DataUnion, 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_struct_fields(data: &Data) -> syn::Result<&Punctuated> { +/// +/// `meta` should be the name of the macro calling this function. +pub fn get_struct_fields<'a>( + data: &'a Data, + meta: &str, +) -> syn::Result<&'a Punctuated> { match data { - Data::Struct(DataStruct { - fields: Fields::Named(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 are supported", + Data::Struct(data_struct) => match &data_struct.fields { + Fields::Named(fields_named) => Ok(&fields_named.named), + Fields::Unnamed(fields_unnamed) => Ok(&fields_unnamed.unnamed), + Fields::Unit => Ok(const { &Punctuated::new() }), + }, + Data::Enum(DataEnum { enum_token, .. }) => Err(Error::new( + enum_token.span(), + format!("#[{meta}] only supports structs, not enums"), + )), + Data::Union(DataUnion { union_token, .. }) => Err(Error::new( + union_token.span(), + format!("#[{meta}] only supports structs, not unions"), )), } } diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index e3f8a3f8c8..7aae1ec74b 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["bevy"] rust-version = "1.85.0" [dependencies] -glam = { version = "0.29", default-features = false, features = ["bytemuck"] } +glam = { version = "0.29.3", default-features = false, features = ["bytemuck"] } thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = [ "from", @@ -37,7 +37,7 @@ rand = "0.8" rand_chacha = "0.3" # Enable the approx feature when testing. bevy_math = { path = ".", default-features = false, features = ["approx"] } -glam = { version = "0.29", default-features = false, features = ["approx"] } +glam = { version = "0.29.3", default-features = false, features = ["approx"] } [features] default = ["std", "rand", "curve"] @@ -77,6 +77,9 @@ rand = ["dep:rand", "dep:rand_distr", "glam/rand"] curve = [] # Enable bevy_reflect (requires alloc) bevy_reflect = ["dep:bevy_reflect", "alloc"] +# Enable libm mathematical functions as a fallback for no_std environments. +# Can be overridden with std feature. +nostd-libm = ["dep:libm", "glam/nostd-libm"] [lints] workspace = true diff --git a/crates/bevy_math/clippy.toml b/crates/bevy_math/clippy.toml index 0fb122e4dc..c1f67e044d 100644 --- a/crates/bevy_math/clippy.toml +++ b/crates/bevy_math/clippy.toml @@ -34,5 +34,6 @@ disallowed-methods = [ { path = "f32::copysign", reason = "use ops::copysign instead for no_std compatibility" }, { path = "f32::round", reason = "use ops::round instead for no_std compatibility" }, { path = "f32::floor", reason = "use ops::floor instead for no_std compatibility" }, + { path = "f32::ceil", reason = "use ops::ceil instead for no_std compatibility" }, { path = "f32::fract", reason = "use ops::fract instead for no_std compatibility" }, ] diff --git a/crates/bevy_math/src/bounding/bounded2d/mod.rs b/crates/bevy_math/src/bounding/bounded2d/mod.rs index bea18f5808..5f11ad5233 100644 --- a/crates/bevy_math/src/bounding/bounded2d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded2d/mod.rs @@ -243,13 +243,9 @@ impl BoundingVolume for Aabb2d { /// and consider storing the original AABB and rotating that every time instead. #[inline(always)] fn rotate_by(&mut self, rotation: impl Into) { - let rotation: Rot2 = rotation.into(); - let abs_rot_mat = Mat2::from_cols( - Vec2::new(rotation.cos, rotation.sin), - Vec2::new(rotation.sin, rotation.cos), - ); - let half_size = abs_rot_mat * self.half_size(); - *self = Self::new(rotation * self.center(), half_size); + let rot_mat = Mat2::from(rotation.into()); + let half_size = rot_mat.abs() * self.half_size(); + *self = Self::new(rot_mat * self.center(), half_size); } } @@ -274,6 +270,8 @@ impl IntersectsVolume for Aabb2d { #[cfg(test)] mod aabb2d_tests { + use approx::assert_relative_eq; + use super::Aabb2d; use crate::{ bounding::{BoundingCircle, BoundingVolume, IntersectsVolume}, @@ -394,6 +392,17 @@ mod aabb2d_tests { assert!(scaled.contains(&a)); } + #[test] + fn rotate() { + let a = Aabb2d { + min: Vec2::new(-2.0, -2.0), + max: Vec2::new(2.0, 2.0), + }; + let rotated = a.rotated_by(core::f32::consts::PI); + assert_relative_eq!(rotated.min, a.min); + assert_relative_eq!(rotated.max, a.max); + } + #[test] fn transform() { let a = Aabb2d { diff --git a/crates/bevy_math/src/bounding/bounded3d/mod.rs b/crates/bevy_math/src/bounding/bounded3d/mod.rs index 5a95b7711f..ca3b359798 100644 --- a/crates/bevy_math/src/bounding/bounded3d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded3d/mod.rs @@ -250,12 +250,7 @@ impl BoundingVolume for Aabb3d { #[inline(always)] fn rotate_by(&mut self, rotation: impl Into) { let rot_mat = Mat3::from_quat(rotation.into()); - let abs_rot_mat = Mat3::from_cols( - rot_mat.x_axis.abs(), - rot_mat.y_axis.abs(), - rot_mat.z_axis.abs(), - ); - let half_size = abs_rot_mat * self.half_size(); + let half_size = rot_mat.abs() * self.half_size(); *self = Self::new(rot_mat * self.center(), half_size); } } @@ -279,6 +274,8 @@ impl IntersectsVolume for Aabb3d { #[cfg(test)] mod aabb3d_tests { + use approx::assert_relative_eq; + use super::Aabb3d; use crate::{ bounding::{BoundingSphere, BoundingVolume, IntersectsVolume}, @@ -398,6 +395,19 @@ mod aabb3d_tests { assert!(scaled.contains(&a)); } + #[test] + fn rotate() { + use core::f32::consts::PI; + let a = Aabb3d { + min: Vec3A::new(-2.0, -2.0, -2.0), + max: Vec3A::new(2.0, 2.0, 2.0), + }; + let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0); + let rotated = a.rotated_by(rotation); + assert_relative_eq!(rotated.min, a.min); + assert_relative_eq!(rotated.max, a.max); + } + #[test] fn transform() { let a = Aabb3d { diff --git a/crates/bevy_math/src/bounding/raycast2d.rs b/crates/bevy_math/src/bounding/raycast2d.rs index e1def01936..c767c6e3fd 100644 --- a/crates/bevy_math/src/bounding/raycast2d.rs +++ b/crates/bevy_math/src/bounding/raycast2d.rs @@ -78,8 +78,8 @@ impl RayCast2d { pub fn circle_intersection_at(&self, circle: &BoundingCircle) -> Option { let offset = self.ray.origin - circle.center; let projected = offset.dot(*self.ray.direction); - let closest_point = offset - projected * *self.ray.direction; - let distance_squared = circle.radius().squared() - closest_point.length_squared(); + let cross = offset.perp_dot(*self.ray.direction); + let distance_squared = circle.radius().squared() - cross.squared(); if distance_squared < 0. || ops::copysign(projected.squared(), -projected) < -distance_squared { diff --git a/crates/bevy_math/src/common_traits.rs b/crates/bevy_math/src/common_traits.rs index a9a8ef910a..4e127f4026 100644 --- a/crates/bevy_math/src/common_traits.rs +++ b/crates/bevy_math/src/common_traits.rs @@ -80,6 +80,8 @@ impl VectorSpace for f32 { /// /// [vector spaces]: VectorSpace #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] pub struct Sum(pub V, pub W); impl Mul for Sum @@ -424,6 +426,9 @@ pub trait HasTangent { } /// A value with its derivative. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] pub struct WithDerivative where T: HasTangent, @@ -436,6 +441,9 @@ where } /// A value together with its first and second derivatives. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] pub struct WithTwoDerivatives where T: HasTangent, diff --git a/crates/bevy_math/src/cubic_splines/mod.rs b/crates/bevy_math/src/cubic_splines/mod.rs index 6f60de774a..0f4082bd09 100644 --- a/crates/bevy_math/src/cubic_splines/mod.rs +++ b/crates/bevy_math/src/cubic_splines/mod.rs @@ -1788,9 +1788,7 @@ mod tests { for (i, (a, b)) in cubic_curve.iter().zip(rational_curve.iter()).enumerate() { assert!( a.distance(*b) < EPSILON, - "Mismatch at {name} value {i}. CubicCurve: {} Converted RationalCurve: {}", - a, - b + "Mismatch at {name} value {i}. CubicCurve: {a} Converted RationalCurve: {b}", ); } } diff --git a/crates/bevy_math/src/curve/derivatives/mod.rs b/crates/bevy_math/src/curve/derivatives/mod.rs index d819443f0d..5949d356e2 100644 --- a/crates/bevy_math/src/curve/derivatives/mod.rs +++ b/crates/bevy_math/src/curve/derivatives/mod.rs @@ -37,24 +37,28 @@ use bevy_reflect::{FromReflect, Reflect}; /// derivatives to be extracted along with values. /// /// This is implemented by implementing [`SampleDerivative`]. -pub trait CurveWithDerivative: SampleDerivative +pub trait CurveWithDerivative: SampleDerivative + Sized where T: HasTangent, { /// This curve, but with its first derivative included in sampling. - fn with_derivative(self) -> impl Curve>; + /// + /// Notably, the output type is a `Curve>`. + fn with_derivative(self) -> SampleDerivativeWrapper; } /// Trait for curves that have a well-defined notion of second derivative, /// allowing for two derivatives to be extracted along with values. /// /// This is implemented by implementing [`SampleTwoDerivatives`]. -pub trait CurveWithTwoDerivatives: SampleTwoDerivatives +pub trait CurveWithTwoDerivatives: SampleTwoDerivatives + Sized where T: HasTangent, { /// This curve, but with its first two derivatives included in sampling. - fn with_two_derivatives(self) -> impl Curve>; + /// + /// Notably, the output type is a `Curve>`. + fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper; } /// A trait for curves that can sample derivatives in addition to values. @@ -210,7 +214,7 @@ where T: HasTangent, C: SampleDerivative, { - fn with_derivative(self) -> impl Curve> { + fn with_derivative(self) -> SampleDerivativeWrapper { SampleDerivativeWrapper(self) } } @@ -220,7 +224,7 @@ where T: HasTangent, C: SampleTwoDerivatives + CurveWithDerivative, { - fn with_two_derivatives(self) -> impl Curve> { + fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper { SampleTwoDerivativesWrapper(self) } } diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 45138f20e2..f5ecf75c08 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -1,6 +1,6 @@ use crate::{ primitives::{Primitive2d, Primitive3d}, - Quat, Rot2, Vec2, Vec3, Vec3A, + Quat, Rot2, Vec2, Vec3, Vec3A, Vec4, }; use core::f32::consts::FRAC_1_SQRT_2; @@ -866,6 +866,195 @@ impl approx::UlpsEq for Dir3A { } } +/// A normalized vector pointing in a direction in 4D space +#[derive(Clone, Copy, Debug, PartialEq, Into)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Debug, PartialEq, Clone) +)] +#[cfg_attr( + all(feature = "serialize", feature = "bevy_reflect"), + reflect(Serialize, Deserialize) +)] +#[doc(alias = "Direction4d")] +pub struct Dir4(Vec4); + +impl Dir4 { + /// A unit vector pointing along the positive X axis + pub const X: Self = Self(Vec4::X); + /// A unit vector pointing along the positive Y axis. + pub const Y: Self = Self(Vec4::Y); + /// A unit vector pointing along the positive Z axis. + pub const Z: Self = Self(Vec4::Z); + /// A unit vector pointing along the positive W axis. + pub const W: Self = Self(Vec4::W); + /// A unit vector pointing along the negative X axis. + pub const NEG_X: Self = Self(Vec4::NEG_X); + /// A unit vector pointing along the negative Y axis. + pub const NEG_Y: Self = Self(Vec4::NEG_Y); + /// A unit vector pointing along the negative Z axis. + pub const NEG_Z: Self = Self(Vec4::NEG_Z); + /// A unit vector pointing along the negative W axis. + pub const NEG_W: Self = Self(Vec4::NEG_W); + /// The directional axes. + pub const AXES: [Self; 4] = [Self::X, Self::Y, Self::Z, Self::W]; + + /// Create a direction from a finite, nonzero [`Vec4`], normalizing it. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the given vector is zero (or very close to zero), infinite, or `NaN`. + pub fn new(value: Vec4) -> Result { + Self::new_and_length(value).map(|(dir, _)| dir) + } + + /// Create a [`Dir4`] from a [`Vec4`] that is already normalized. + /// + /// # Warning + /// + /// `value` must be normalized, i.e its length must be `1.0`. + pub fn new_unchecked(value: Vec4) -> Self { + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir4::new_unchecked` is not normalized.", + value.length_squared(), + ); + Self(value) + } + + /// Create a direction from a finite, nonzero [`Vec4`], normalizing it and + /// also returning its original length. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the given vector is zero (or very close to zero), infinite, or `NaN`. + pub fn new_and_length(value: Vec4) -> Result<(Self, f32), InvalidDirectionError> { + let length = value.length(); + let direction = (length.is_finite() && length > 0.0).then_some(value / length); + + direction + .map(|dir| (Self(dir), length)) + .ok_or(InvalidDirectionError::from_length(length)) + } + + /// Create a direction from its `x`, `y`, `z`, and `w` components. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. + pub fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Result { + Self::new(Vec4::new(x, y, z, w)) + } + + /// Create a direction from its `x`, `y`, `z`, and `w` components, assuming the resulting vector is normalized. + /// + /// # Warning + /// + /// The vector produced from `x`, `y`, `z`, and `w` must be normalized, i.e its length must be `1.0`. + pub fn from_xyzw_unchecked(x: f32, y: f32, z: f32, w: f32) -> Self { + Self::new_unchecked(Vec4::new(x, y, z, w)) + } + + /// Returns the inner [`Vec4`] + pub const fn as_vec4(&self) -> Vec4 { + self.0 + } + + /// Returns `self` after an approximate normalization, assuming the value is already nearly normalized. + /// Useful for preventing numerical error accumulation. + #[inline] + pub fn fast_renormalize(self) -> Self { + // We numerically approximate the inverse square root by a Taylor series around 1 + // As we expect the error (x := length_squared - 1) to be small + // inverse_sqrt(length_squared) = (1 + x)^(-1/2) = 1 - 1/2 x + O(x²) + // inverse_sqrt(length_squared) ≈ 1 - 1/2 (length_squared - 1) = 1/2 (3 - length_squared) + + // Iterative calls to this method quickly converge to a normalized value, + // so long as the denormalization is not large ~ O(1/10). + // One iteration can be described as: + // l_sq <- l_sq * (1 - 1/2 (l_sq - 1))²; + // Rewriting in terms of the error x: + // 1 + x <- (1 + x) * (1 - 1/2 x)² + // 1 + x <- (1 + x) * (1 - x + 1/4 x²) + // 1 + x <- 1 - x + 1/4 x² + x - x² + 1/4 x³ + // x <- -1/4 x² (3 - x) + // If the error is small, say in a range of (-1/2, 1/2), then: + // |-1/4 x² (3 - x)| <= (3/4 + 1/4 * |x|) * x² <= (3/4 + 1/4 * 1/2) * x² < x² < 1/2 x + // Therefore the sequence of iterates converges to 0 error as a second order method. + + let length_squared = self.0.length_squared(); + Self(self * (0.5 * (3.0 - length_squared))) + } +} + +impl TryFrom for Dir4 { + type Error = InvalidDirectionError; + + fn try_from(value: Vec4) -> Result { + Self::new(value) + } +} + +impl core::ops::Deref for Dir4 { + type Target = Vec4; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::Neg for Dir4 { + type Output = Self; + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl core::ops::Mul for Dir4 { + type Output = Vec4; + fn mul(self, rhs: f32) -> Self::Output { + self.0 * rhs + } +} + +impl core::ops::Mul for f32 { + type Output = Vec4; + fn mul(self, rhs: Dir4) -> Self::Output { + self * rhs.0 + } +} + +#[cfg(feature = "approx")] +impl approx::AbsDiffEq for Dir4 { + type Epsilon = f32; + fn default_epsilon() -> f32 { + f32::EPSILON + } + fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { + self.as_ref().abs_diff_eq(other.as_ref(), epsilon) + } +} + +#[cfg(feature = "approx")] +impl approx::RelativeEq for Dir4 { + fn default_max_relative() -> f32 { + f32::EPSILON + } + fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { + self.as_ref() + .relative_eq(other.as_ref(), epsilon, max_relative) + } +} + +#[cfg(feature = "approx")] +impl approx::UlpsEq for Dir4 { + fn default_max_ulps() -> u32 { + 4 + } + + fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} + #[cfg(test)] #[cfg(feature = "approx")] mod tests { @@ -1090,4 +1279,48 @@ mod tests { ); assert!(dir_b.is_normalized(), "Renormalisation did not work."); } + + #[test] + fn dir4_creation() { + assert_eq!(Dir4::new(Vec4::X * 12.5), Ok(Dir4::X)); + assert_eq!( + Dir4::new(Vec4::new(0.0, 0.0, 0.0, 0.0)), + Err(InvalidDirectionError::Zero) + ); + assert_eq!( + Dir4::new(Vec4::new(f32::INFINITY, 0.0, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Dir4::new(Vec4::new(f32::NEG_INFINITY, 0.0, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Dir4::new(Vec4::new(f32::NAN, 0.0, 0.0, 0.0)), + Err(InvalidDirectionError::NaN) + ); + assert_eq!(Dir4::new_and_length(Vec4::X * 6.5), Ok((Dir4::X, 6.5))); + } + + #[test] + fn dir4_renorm() { + // Evil denormalized matrix + let mat4 = bevy_math::Mat4::from_quat(Quat::from_euler(glam::EulerRot::XYZ, 1.0, 2.0, 3.0)) + * (1.0 + 1e-5); + let mut dir_a = Dir4::from_xyzw(1., 1., 0., 0.).unwrap(); + let mut dir_b = Dir4::from_xyzw(1., 1., 0., 0.).unwrap(); + // We test that renormalizing an already normalized dir doesn't do anything + assert_relative_eq!(dir_b, dir_b.fast_renormalize(), epsilon = 0.000001); + for _ in 0..50 { + dir_a = Dir4(mat4 * *dir_a); + dir_b = Dir4(mat4 * *dir_b); + dir_b = dir_b.fast_renormalize(); + } + // `dir_a` should've gotten denormalized, meanwhile `dir_b` should stay normalized. + assert!( + !dir_a.is_normalized(), + "Denormalization doesn't work, test is faulty" + ); + assert!(dir_b.is_normalized(), "Renormalisation did not work."); + } } diff --git a/crates/bevy_math/src/ops.rs b/crates/bevy_math/src/ops.rs index e9d27ac54a..3a7765939d 100644 --- a/crates/bevy_math/src/ops.rs +++ b/crates/bevy_math/src/ops.rs @@ -19,7 +19,7 @@ // - `f32::gamma` // - `f32::ln_gamma` -#[cfg(not(feature = "libm"))] +#[cfg(all(not(feature = "libm"), feature = "std"))] #[expect( clippy::disallowed_methods, reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule." @@ -233,7 +233,7 @@ mod std_ops { } } -#[cfg(feature = "libm")] +#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))] mod libm_ops { /// Raises a number to a floating point power. @@ -448,7 +448,7 @@ mod libm_ops { } } -#[cfg(all(feature = "libm", not(feature = "std")))] +#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))] mod libm_ops_for_no_std { //! Provides standardized names for [`f32`] operations which may not be //! supported on `no_std` platforms. @@ -510,6 +510,14 @@ mod libm_ops_for_no_std { libm::floorf(x) } + /// Returns the smallest integer greater than or equal to `x`. + /// + /// Precision is specified when the `libm` feature is enabled. + #[inline(always)] + pub fn ceil(x: f32) -> f32 { + libm::ceilf(x) + } + /// Returns the fractional part of `x`. /// /// This function always returns the precise result. @@ -581,6 +589,14 @@ mod std_ops_for_no_std { f32::floor(x) } + /// Returns the smallest integer greater than or equal to `x`. + /// + /// This function always returns the precise result. + #[inline(always)] + pub fn ceil(x: f32) -> f32 { + f32::ceil(x) + } + /// Returns the fractional part of `x`. /// /// This function always returns the precise result. @@ -590,20 +606,24 @@ mod std_ops_for_no_std { } } -#[cfg(feature = "libm")] +#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))] pub use libm_ops::*; -#[cfg(not(feature = "libm"))] +#[cfg(all(not(feature = "libm"), feature = "std"))] pub use std_ops::*; #[cfg(feature = "std")] pub use std_ops_for_no_std::*; -#[cfg(all(feature = "libm", not(feature = "std")))] +#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))] pub use libm_ops_for_no_std::*; -#[cfg(all(not(feature = "libm"), not(feature = "std")))] -compile_error!("Either the `libm` feature or the `std` feature must be enabled."); +#[cfg(all( + not(feature = "libm"), + not(feature = "std"), + not(feature = "nostd-libm") +))] +compile_error!("Either the `libm`, `std`, or `nostd-libm` feature must be enabled."); /// This extension trait covers shortfall in determinacy from the lack of a `libm` counterpart /// to `f32::powi`. Use this for the common small exponents. diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 613345bcd8..d666849840 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -1243,13 +1243,6 @@ impl Segment2d { } } - /// Create a new `Segment2d` from its endpoints and compute its geometric center. - #[inline(always)] - #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] - pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { - (Self::new(point1, point2), (point1 + point2) / 2.) - } - /// Create a new `Segment2d` centered at the origin with the given direction and length. /// /// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`. diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index a36db0ade5..ea5ccd6e2d 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -381,13 +381,6 @@ impl Segment3d { } } - /// Create a new `Segment3d` from its endpoints and compute its geometric center. - #[inline(always)] - #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] - pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { - (Self::new(point1, point2), (point1 + point2) / 2.) - } - /// Create a new `Segment3d` centered at the origin with the given direction and length. /// /// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`. diff --git a/crates/bevy_math/src/primitives/serde.rs b/crates/bevy_math/src/primitives/serde.rs index 7db6be9700..a1b678132e 100644 --- a/crates/bevy_math/src/primitives/serde.rs +++ b/crates/bevy_math/src/primitives/serde.rs @@ -31,7 +31,7 @@ pub(crate) mod array { type Value = [T; N]; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_fmt(format_args!("an array of length {}", N)) + formatter.write_fmt(format_args!("an array of length {N}")) } #[inline] diff --git a/crates/bevy_mesh/Cargo.toml b/crates/bevy_mesh/Cargo.toml index a57abc6e79..2ccb65cdb4 100644 --- a/crates/bevy_mesh/Cargo.toml +++ b/crates/bevy_mesh/Cargo.toml @@ -19,7 +19,7 @@ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } diff --git a/crates/bevy_mesh/src/lib.rs b/crates/bevy_mesh/src/lib.rs index 83bc30df35..58702d7d8b 100644 --- a/crates/bevy_mesh/src/lib.rs +++ b/crates/bevy_mesh/src/lib.rs @@ -17,6 +17,7 @@ pub use mesh::*; pub use mikktspace::*; pub use primitives::*; pub use vertex::*; +pub use wgpu_types::VertexFormat; bitflags! { /// Our base mesh pipeline key bits start from the highest bit and go diff --git a/crates/bevy_mesh/src/primitives/dim2.rs b/crates/bevy_mesh/src/primitives/dim2.rs index 61eaaf8ef8..e543f8a195 100644 --- a/crates/bevy_mesh/src/primitives/dim2.rs +++ b/crates/bevy_mesh/src/primitives/dim2.rs @@ -1176,7 +1176,7 @@ impl From for Mesh { #[cfg(test)] mod tests { use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd}; - use bevy_platform_support::collections::HashSet; + use bevy_platform::collections::HashSet; use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; diff --git a/crates/bevy_mesh/src/vertex.rs b/crates/bevy_mesh/src/vertex.rs index 253c04af45..949e355b4c 100644 --- a/crates/bevy_mesh/src/vertex.rs +++ b/crates/bevy_mesh/src/vertex.rs @@ -2,7 +2,7 @@ use alloc::sync::Arc; use bevy_derive::EnumVariantMeta; use bevy_ecs::resource::Resource; use bevy_math::Vec3; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bytemuck::cast_slice; use core::hash::{Hash, Hasher}; use thiserror::Error; diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index 08eca4cc69..fbca931fe2 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -22,7 +22,7 @@ std = ["glam/std"] libm = ["glam/libm", "dep:libm"] [dependencies] -glam = { version = "0.29.0", default-features = false } +glam = { version = "0.29.3", default-features = false } libm = { version = "0.2", default-features = false, optional = true } [[example]] diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index a726eb5bc8..b246b9668d 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -756,7 +756,7 @@ unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) return false; } while i < (*pg1).iNrFaces as usize && bStillSame { - bStillSame = if (*pg1).pTriMembers[i] == (*pg2).pTriMembers[i] { + bStillSame = if (&(*pg1).pTriMembers)[i] == (&(*pg2).pTriMembers)[i] { true } else { false diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 9b9860b60f..82642812b4 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -46,7 +46,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", optional = true } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index 17a46e18d0..c1f02fc921 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -49,7 +49,7 @@ const ROOT_2: f32 = 1.41421356; // √2 // the exponential falloff of atmospheric density. const MIDPOINT_RATIO: f32 = 0.3; -// LUT UV PARAMATERIZATIONS +// LUT UV PARAMETERIZATIONS fn unit_to_sub_uvs(val: vec2, resolution: vec2) -> vec2 { return (val + 0.5f / resolution) * (resolution / (resolution + 1.0f)); @@ -277,11 +277,11 @@ fn sample_local_inscattering(local_atmosphere: AtmosphereSample, ray_dir: vec3, transmittance: vec3) -> vec3 { +fn sample_sun_radiance(ray_dir_ws: vec3) -> vec3 { let r = view_radius(); let mu_view = ray_dir_ws.y; let shadow_factor = f32(!ray_intersects_ground(r, mu_view)); - var sun_illuminance = vec3(0.0); + var sun_radiance = vec3(0.0); for (var light_i: u32 = 0u; light_i < lights.n_directional_lights; light_i++) { let light = &lights.directional_lights[light_i]; let neg_LdotV = dot((*light).direction_to_light, ray_dir_ws); @@ -289,9 +289,9 @@ fn sample_sun_illuminance(ray_dir_ws: vec3, transmittance: vec3) -> ve let pixel_size = fwidth(angle_to_sun); let factor = smoothstep(0.0, -pixel_size * ROOT_2, angle_to_sun - SUN_ANGULAR_SIZE * 0.5); let sun_solid_angle = (SUN_ANGULAR_SIZE * SUN_ANGULAR_SIZE) * 4.0 * FRAC_PI; - sun_illuminance += ((*light).color.rgb / sun_solid_angle) * factor * shadow_factor; + sun_radiance += ((*light).color.rgb / sun_solid_angle) * factor * shadow_factor; } - return sun_illuminance * transmittance * view.exposure; + return sun_radiance; } // TRANSFORM UTILITIES diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index e445a5adc8..8b08751428 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -25,6 +25,10 @@ //! at once is untested, and might not be physically accurate. These may be //! integrated into a single module in the future. //! +//! On web platforms, atmosphere rendering will look slightly different. Specifically, when calculating how light travels +//! through the atmosphere, we use a simpler averaging technique instead of the more +//! complex blending operations. This difference will be resolved for WebGPU in a future release. +//! //! [Shadertoy]: https://www.shadertoy.com/view/slSXRW //! //! [Unreal Engine Implementation]: https://github.com/sebh/UnrealEngineSkyAtmosphere @@ -33,7 +37,7 @@ mod node; pub mod resources; use bevy_app::{App, Plugin}; -use bevy_asset::load_internal_asset; +use bevy_asset::embedded_asset; use bevy_core_pipeline::core_3d::graph::Node3d; use bevy_ecs::{ component::Component, @@ -45,16 +49,16 @@ use bevy_math::{UVec2, UVec3, Vec3}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::UniformComponentPlugin, + load_shader_library, render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines}, - renderer::RenderDevice, - settings::WgpuFeatures, + view::Hdr, }; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, render_graph::{RenderGraphApp, ViewNodeRunner}, - render_resource::{Shader, TextureFormat, TextureUsages}, + render_resource::{TextureFormat, TextureUsages}, renderer::RenderAdapter, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_core_pipeline::core_3d::{graph::Core3d, Camera3d}; @@ -72,76 +76,21 @@ use self::{ }, }; -mod shaders { - use bevy_asset::{weak_handle, Handle}; - use bevy_render::render_resource::Shader; - - pub const TYPES: Handle = weak_handle!("ef7e147e-30a0-4513-bae3-ddde2a6c20c5"); - pub const FUNCTIONS: Handle = weak_handle!("7ff93872-2ee9-4598-9f88-68b02fef605f"); - pub const BRUNETON_FUNCTIONS: Handle = - weak_handle!("e2dccbb0-7322-444a-983b-e74d0a08bcda"); - pub const BINDINGS: Handle = weak_handle!("bcc55ce5-0fc4-451e-8393-1b9efd2612c4"); - - pub const TRANSMITTANCE_LUT: Handle = - weak_handle!("a4187282-8cb1-42d3-889c-cbbfb6044183"); - pub const MULTISCATTERING_LUT: Handle = - weak_handle!("bde3a71a-73e9-49fe-a379-a81940c67a1e"); - pub const SKY_VIEW_LUT: Handle = weak_handle!("f87e007a-bf4b-4f99-9ef0-ac21d369f0e5"); - pub const AERIAL_VIEW_LUT: Handle = - weak_handle!("a3daf030-4b64-49ae-a6a7-354489597cbe"); - pub const RENDER_SKY: Handle = weak_handle!("09422f46-d0f7-41c1-be24-121c17d6e834"); -} - #[doc(hidden)] pub struct AtmospherePlugin; impl Plugin for AtmospherePlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, shaders::TYPES, "types.wgsl", Shader::from_wgsl); - load_internal_asset!(app, shaders::FUNCTIONS, "functions.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - shaders::BRUNETON_FUNCTIONS, - "bruneton_functions.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "types.wgsl"); + load_shader_library!(app, "functions.wgsl"); + load_shader_library!(app, "bruneton_functions.wgsl"); + load_shader_library!(app, "bindings.wgsl"); - load_internal_asset!(app, shaders::BINDINGS, "bindings.wgsl", Shader::from_wgsl); - - load_internal_asset!( - app, - shaders::TRANSMITTANCE_LUT, - "transmittance_lut.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - shaders::MULTISCATTERING_LUT, - "multiscattering_lut.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - shaders::SKY_VIEW_LUT, - "sky_view_lut.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - shaders::AERIAL_VIEW_LUT, - "aerial_view_lut.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - shaders::RENDER_SKY, - "render_sky.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "transmittance_lut.wgsl"); + embedded_asset!(app, "multiscattering_lut.wgsl"); + embedded_asset!(app, "sky_view_lut.wgsl"); + embedded_asset!(app, "aerial_view_lut.wgsl"); + embedded_asset!(app, "render_sky.wgsl"); app.register_type::() .register_type::() @@ -159,15 +108,6 @@ impl Plugin for AtmospherePlugin { }; let render_adapter = render_app.world().resource::(); - let render_device = render_app.world().resource::(); - - if !render_device - .features() - .contains(WgpuFeatures::DUAL_SOURCE_BLENDING) - { - warn!("AtmospherePlugin not loaded. GPU lacks support for dual-source blending."); - return; - } if !render_adapter .get_downlevel_capabilities() @@ -197,11 +137,11 @@ impl Plugin for AtmospherePlugin { .add_systems( Render, ( - configure_camera_depth_usages.in_set(RenderSet::ManageViews), - queue_render_sky_pipelines.in_set(RenderSet::Queue), - prepare_atmosphere_textures.in_set(RenderSet::PrepareResources), - prepare_atmosphere_transforms.in_set(RenderSet::PrepareResources), - prepare_atmosphere_bind_groups.in_set(RenderSet::PrepareBindGroups), + configure_camera_depth_usages.in_set(RenderSystems::ManageViews), + queue_render_sky_pipelines.in_set(RenderSystems::Queue), + prepare_atmosphere_textures.in_set(RenderSystems::PrepareResources), + prepare_atmosphere_transforms.in_set(RenderSystems::PrepareResources), + prepare_atmosphere_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) .add_render_graph_node::>( @@ -253,7 +193,7 @@ impl Plugin for AtmospherePlugin { /// from the planet's surface, ozone only exists in a band centered at a fairly /// high altitude. #[derive(Clone, Component, Reflect, ShaderType)] -#[require(AtmosphereSettings)] +#[require(AtmosphereSettings, Hdr)] #[reflect(Clone, Default)] pub struct Atmosphere { /// Radius of the planet diff --git a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl index 314037f887..e488656df4 100644 --- a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl +++ b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl @@ -5,7 +5,7 @@ sample_transmittance_lut, sample_transmittance_lut_segment, sample_sky_view_lut, direction_world_to_atmosphere, uv_to_ray_direction, uv_to_ndc, sample_aerial_view_lut, - view_radius, sample_sun_illuminance, ndc_to_camera_dist + view_radius, sample_sun_radiance, ndc_to_camera_dist }, }; #import bevy_render::view::View; @@ -20,7 +20,9 @@ struct RenderSkyOutput { @location(0) inscattering: vec4, +#ifdef DUAL_SOURCE_BLENDING @location(0) @second_blend_source transmittance: vec4, +#endif } @fragment @@ -33,15 +35,24 @@ fn main(in: FullscreenVertexOutput) -> RenderSkyOutput { var transmittance: vec3; var inscattering: vec3; + + let sun_radiance = sample_sun_radiance(ray_dir_ws.xyz); + if depth == 0.0 { let ray_dir_as = direction_world_to_atmosphere(ray_dir_ws.xyz); transmittance = sample_transmittance_lut(r, mu); inscattering += sample_sky_view_lut(r, ray_dir_as); - inscattering += sample_sun_illuminance(ray_dir_ws.xyz, transmittance); + inscattering += sun_radiance * transmittance * view.exposure; } else { let t = ndc_to_camera_dist(vec3(uv_to_ndc(in.uv), depth)); inscattering = sample_aerial_view_lut(in.uv, t); transmittance = sample_transmittance_lut_segment(r, mu, t); } +#ifdef DUAL_SOURCE_BLENDING return RenderSkyOutput(vec4(inscattering, 0.0), vec4(transmittance, 1.0)); +#else + let mean_transmittance = (transmittance.r + transmittance.g + transmittance.b) / 3.0; + return RenderSkyOutput(vec4(inscattering, mean_transmittance)); +#endif + } diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index 71bf26da2d..8c57fb8eb9 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -1,3 +1,4 @@ +use bevy_asset::{load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::Camera3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; @@ -21,7 +22,7 @@ use bevy_render::{ use crate::{GpuLights, LightMeta}; -use super::{shaders, Atmosphere, AtmosphereSettings}; +use super::{Atmosphere, AtmosphereSettings}; #[derive(Resource)] pub(crate) struct AtmosphereBindGroupLayouts { @@ -35,6 +36,7 @@ pub(crate) struct AtmosphereBindGroupLayouts { pub(crate) struct RenderSkyBindGroupLayouts { pub render_sky: BindGroupLayout, pub render_sky_msaa: BindGroupLayout, + pub shader: Handle, } impl FromWorld for AtmosphereBindGroupLayouts { @@ -203,6 +205,7 @@ impl FromWorld for RenderSkyBindGroupLayouts { Self { render_sky, render_sky_msaa, + shader: load_embedded_asset!(world, "render_sky.wgsl"), } } } @@ -273,7 +276,7 @@ impl FromWorld for AtmosphereLutPipelines { label: Some("transmittance_lut_pipeline".into()), layout: vec![layouts.transmittance_lut.clone()], push_constant_ranges: vec![], - shader: shaders::TRANSMITTANCE_LUT, + shader: load_embedded_asset!(world, "transmittance_lut.wgsl"), shader_defs: vec![], entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -284,7 +287,7 @@ impl FromWorld for AtmosphereLutPipelines { label: Some("multi_scattering_lut_pipeline".into()), layout: vec![layouts.multiscattering_lut.clone()], push_constant_ranges: vec![], - shader: shaders::MULTISCATTERING_LUT, + shader: load_embedded_asset!(world, "multiscattering_lut.wgsl"), shader_defs: vec![], entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -294,7 +297,7 @@ impl FromWorld for AtmosphereLutPipelines { label: Some("sky_view_lut_pipeline".into()), layout: vec![layouts.sky_view_lut.clone()], push_constant_ranges: vec![], - shader: shaders::SKY_VIEW_LUT, + shader: load_embedded_asset!(world, "sky_view_lut.wgsl"), shader_defs: vec![], entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -304,7 +307,7 @@ impl FromWorld for AtmosphereLutPipelines { label: Some("aerial_view_lut_pipeline".into()), layout: vec![layouts.aerial_view_lut.clone()], push_constant_ranges: vec![], - shader: shaders::AERIAL_VIEW_LUT, + shader: load_embedded_asset!(world, "aerial_view_lut.wgsl"), shader_defs: vec![], entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -325,7 +328,7 @@ pub(crate) struct RenderSkyPipelineId(pub CachedRenderPipelineId); #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub(crate) struct RenderSkyPipelineKey { pub msaa_samples: u32, - pub hdr: bool, + pub dual_source_blending: bool, } impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { @@ -337,10 +340,16 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { if key.msaa_samples > 1 { shader_defs.push("MULTISAMPLED".into()); } - if key.hdr { - shader_defs.push("TONEMAP_IN_SHADER".into()); + if key.dual_source_blending { + shader_defs.push("DUAL_SOURCE_BLENDING".into()); } + let dst_factor = if key.dual_source_blending { + BlendFactor::Src1 + } else { + BlendFactor::SrcAlpha + }; + RenderPipelineDescriptor { label: Some(format!("render_sky_pipeline_{}", key.msaa_samples).into()), layout: vec![if key.msaa_samples == 1 { @@ -359,7 +368,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { }, zero_initialize_workgroup_memory: false, fragment: Some(FragmentState { - shader: shaders::RENDER_SKY.clone(), + shader: self.shader.clone(), shader_defs, entry_point: "main".into(), targets: vec![Some(ColorTargetState { @@ -367,7 +376,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { blend: Some(BlendState { color: BlendComponent { src_factor: BlendFactor::One, - dst_factor: BlendFactor::Src1, + dst_factor, operation: BlendOperation::Add, }, alpha: BlendComponent { @@ -384,19 +393,22 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { } pub(super) fn queue_render_sky_pipelines( - views: Query<(Entity, &Camera, &Msaa), With>, + views: Query<(Entity, &Msaa), (With, With)>, pipeline_cache: Res, layouts: Res, mut specializer: ResMut>, + render_device: Res, mut commands: Commands, ) { - for (entity, camera, msaa) in &views { + for (entity, msaa) in &views { let id = specializer.specialize( &pipeline_cache, &layouts, RenderSkyPipelineKey { msaa_samples: msaa.samples(), - hdr: camera.hdr, + dual_source_blending: render_device + .features() + .contains(WgpuFeatures::DUAL_SOURCE_BLENDING), }, ); commands.entity(entity).insert(RenderSkyPipelineId(id)); diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 5657cd29d3..3113333be3 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -5,7 +5,7 @@ use core::num::NonZero; use bevy_core_pipeline::core_3d::Camera3d; use bevy_ecs::{ component::Component, - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, query::{With, Without}, reflect::ReflectComponent, resource::Resource, @@ -13,7 +13,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::{uvec4, AspectRatio, UVec2, UVec3, UVec4, Vec3Swizzles as _, Vec4}; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::Camera, diff --git a/crates/bevy_pbr/src/components.rs b/crates/bevy_pbr/src/components.rs index d70da7cd95..fca31b3b03 100644 --- a/crates/bevy_pbr/src/components.rs +++ b/crates/bevy_pbr/src/components.rs @@ -1,6 +1,6 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Component; -use bevy_ecs::entity::{hash_map::EntityHashMap, Entity}; +use bevy_ecs::entity::{Entity, EntityHashMap}; use bevy_ecs::reflect::ReflectComponent; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::sync_world::MainEntity; diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index 67de430fe4..dd77d2088d 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -17,11 +17,11 @@ use core::{num::NonZero, ops::Deref}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; +use bevy_asset::{AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, prelude::ReflectComponent, query::With, resource::Resource, @@ -30,20 +30,21 @@ use bevy_ecs::{ }; use bevy_image::Image; use bevy_math::Mat4; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, + load_shader_library, render_asset::RenderAssets, render_resource::{ binding_types, BindGroupLayoutEntryBuilder, Buffer, BufferUsages, RawBufferVec, Sampler, - SamplerBindingType, Shader, ShaderType, TextureSampleType, TextureView, + SamplerBindingType, ShaderType, TextureSampleType, TextureView, }, renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::RenderEntity, texture::{FallbackImage, GpuImage}, view::{self, ViewVisibility, Visibility, VisibilityClass}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bytemuck::{Pod, Zeroable}; @@ -52,10 +53,6 @@ use crate::{ binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta, LightVisibilityClass, }; -/// The handle to the `clustered.wgsl` shader. -pub(crate) const CLUSTERED_DECAL_SHADER_HANDLE: Handle = - weak_handle!("87929002-3509-42f1-8279-2d2765dd145c"); - /// The maximum number of decals that can be present in a view. /// /// This number is currently relatively low in order to work around the lack of @@ -152,12 +149,7 @@ impl Default for DecalsBuffer { impl Plugin for ClusteredDecalPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - CLUSTERED_DECAL_SHADER_HANDLE, - "clustered.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "clustered.wgsl"); app.add_plugins(ExtractComponentPlugin::::default()) .register_type::(); @@ -173,10 +165,13 @@ impl Plugin for ClusteredDecalPlugin { .add_systems( Render, prepare_decals - .in_set(RenderSet::ManageViews) + .in_set(RenderSystems::ManageViews) .after(prepare_lights), ) - .add_systems(Render, upload_decals.in_set(RenderSet::PrepareResources)); + .add_systems( + Render, + upload_decals.in_set(RenderSystems::PrepareResources), + ); } } diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index 8490017fbb..7d82946346 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -3,36 +3,32 @@ use crate::{ MaterialPlugin, StandardMaterial, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, Assets, Handle}; +use bevy_asset::{weak_handle, Asset, Assets, Handle}; use bevy_ecs::component::Component; use bevy_math::{prelude::Rectangle, Quat, Vec2, Vec3}; use bevy_reflect::{Reflect, TypePath}; +use bevy_render::load_shader_library; +use bevy_render::render_asset::RenderAssets; +use bevy_render::render_resource::{AsBindGroupShaderType, ShaderType}; +use bevy_render::texture::GpuImage; use bevy_render::{ alpha::AlphaMode, mesh::{Mesh, Mesh3d, MeshBuilder, MeshVertexBufferLayoutRef, Meshable}, render_resource::{ - AsBindGroup, CompareFunction, RenderPipelineDescriptor, Shader, - SpecializedMeshPipelineError, + AsBindGroup, CompareFunction, RenderPipelineDescriptor, SpecializedMeshPipelineError, }, RenderDebugFlags, }; const FORWARD_DECAL_MESH_HANDLE: Handle = weak_handle!("afa817f9-1869-4e0c-ac0d-d8cd1552d38a"); -const FORWARD_DECAL_SHADER_HANDLE: Handle = - weak_handle!("f8dfbef4-d88b-42ae-9af4-d9661e9f1648"); /// Plugin to render [`ForwardDecal`]s. pub struct ForwardDecalPlugin; impl Plugin for ForwardDecalPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - FORWARD_DECAL_SHADER_HANDLE, - "forward_decal.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "forward_decal.wgsl"); app.register_type::(); @@ -67,7 +63,7 @@ impl Plugin for ForwardDecalPlugin { /// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's /// texture with extra transparent pixels on the edges. #[derive(Component, Reflect)] -#[require(Mesh3d(|| Mesh3d(FORWARD_DECAL_MESH_HANDLE)))] +#[require(Mesh3d(FORWARD_DECAL_MESH_HANDLE))] pub struct ForwardDecal; /// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension. @@ -86,16 +82,36 @@ pub type ForwardDecalMaterial = ExtendedMaterial for ForwardDecalMaterialExt { + fn as_bind_group_shader_type( + &self, + _images: &RenderAssets, + ) -> ForwardDecalMaterialExtUniform { + ForwardDecalMaterialExtUniform { + inv_depth_fade_factor: 1.0 / self.depth_fade_factor.max(0.001), + } + } +} + impl MaterialExtension for ForwardDecalMaterialExt { fn alpha_mode() -> Option { Some(AlphaMode::Blend) diff --git a/crates/bevy_pbr/src/decal/forward_decal.wgsl b/crates/bevy_pbr/src/decal/forward_decal.wgsl index dbc6bbc1c4..ce24d57bf5 100644 --- a/crates/bevy_pbr/src/decal/forward_decal.wgsl +++ b/crates/bevy_pbr/src/decal/forward_decal.wgsl @@ -11,7 +11,7 @@ #import bevy_render::maths::project_onto @group(2) @binding(200) -var depth_fade_factor: f32; +var inv_depth_fade_factor: f32; struct ForwardDecalInformation { world_position: vec4, @@ -46,7 +46,7 @@ fn get_forward_decal_info(in: VertexOutput) -> ForwardDecalInformation { let uv = in.uv + delta_uv; let world_position = vec4(in.world_position.xyz + V * diff_depth_abs, in.world_position.w); - let alpha = saturate(1.0 - normal_depth * depth_fade_factor); + let alpha = saturate(1.0 - (normal_depth * inv_depth_fade_factor)); return ForwardDecalInformation(world_position, uv, alpha); } diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index c7d7e04636..65be474e65 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -10,7 +10,7 @@ use crate::{ ViewLightsUniformOffset, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, deferred::{ @@ -29,14 +29,11 @@ use bevy_render::{ render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderContext, RenderDevice}, view::{ExtractedView, ViewTarget, ViewUniformOffset}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; pub struct DeferredPbrLightingPlugin; -pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle = - weak_handle!("f4295279-8890-4748-b654-ca4d2183df1c"); - pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1; /// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass. @@ -100,12 +97,7 @@ impl Plugin for DeferredPbrLightingPlugin { )) .add_systems(PostUpdate, insert_deferred_lighting_pass_id_component); - load_internal_asset!( - app, - DEFERRED_LIGHTING_SHADER_HANDLE, - "deferred_lighting.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "deferred_lighting.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -115,7 +107,7 @@ impl Plugin for DeferredPbrLightingPlugin { .init_resource::>() .add_systems( Render, - (prepare_deferred_lighting_pipelines.in_set(RenderSet::Prepare),), + (prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),), ) .add_render_graph_node::>( Core3d, @@ -237,6 +229,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode { pub struct DeferredLightingLayout { mesh_pipeline: MeshPipeline, bind_group_layout_1: BindGroupLayout, + deferred_lighting_shader: Handle, } #[derive(Component)] @@ -345,6 +338,10 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL { shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into()); } + if self.mesh_pipeline.binding_arrays_are_usable { + shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); + shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); + } #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); @@ -356,13 +353,13 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { self.bind_group_layout_1.clone(), ], vertex: VertexState { - shader: DEFERRED_LIGHTING_SHADER_HANDLE, + shader: self.deferred_lighting_shader.clone(), shader_defs: shader_defs.clone(), entry_point: "vertex".into(), buffers: Vec::new(), }, fragment: Some(FragmentState { - shader: DEFERRED_LIGHTING_SHADER_HANDLE, + shader: self.deferred_lighting_shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -412,6 +409,7 @@ impl FromWorld for DeferredLightingLayout { Self { mesh_pipeline: world.resource::().clone(), bind_group_layout_1: layout, + deferred_lighting_shader: load_embedded_asset!(world, "deferred_lighting.wgsl"), } } } diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index b79f3c3d2c..e01dd0ff14 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -1,13 +1,16 @@ +use alloc::borrow::Cow; + use bevy_asset::{Asset, Handle}; use bevy_ecs::system::SystemParamItem; +use bevy_platform::{collections::HashSet, hash::FixedHasher}; use bevy_reflect::{impl_type_path, Reflect}; use bevy_render::{ alpha::AlphaMode, mesh::MeshVertexBufferLayoutRef, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroupLayout, BindlessDescriptor, - BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader, ShaderRef, - SpecializedMeshPipelineError, UnpreparedBindGroup, + AsBindGroup, AsBindGroupError, BindGroupLayout, BindGroupLayoutEntry, BindlessDescriptor, + BindlessResourceType, BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader, + ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup, }, renderer::RenderDevice, }; @@ -156,11 +159,24 @@ impl AsBindGroup for ExtendedMaterial { type Param = (::Param, ::Param); fn bindless_slot_count() -> Option { - // For now, disable bindless in `ExtendedMaterial`. - if B::bindless_slot_count().is_some() && E::bindless_slot_count().is_some() { - panic!("Bindless extended materials are currently unsupported") + // We only enable bindless if both the base material and its extension + // are bindless. If we do enable bindless, we choose the smaller of the + // two slab size limits. + match (B::bindless_slot_count()?, E::bindless_slot_count()?) { + (BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Auto) => { + Some(BindlessSlabResourceLimit::Auto) + } + (BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Custom(limit)) + | (BindlessSlabResourceLimit::Custom(limit), BindlessSlabResourceLimit::Auto) => { + Some(BindlessSlabResourceLimit::Custom(limit)) + } + ( + BindlessSlabResourceLimit::Custom(base_limit), + BindlessSlabResourceLimit::Custom(extended_limit), + ) => Some(BindlessSlabResourceLimit::Custom( + base_limit.min(extended_limit), + )), } - None } fn unprepared_bind_group( @@ -168,15 +184,28 @@ impl AsBindGroup for ExtendedMaterial { layout: &BindGroupLayout, render_device: &RenderDevice, (base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>, - _: bool, + mut force_non_bindless: bool, ) -> Result, AsBindGroupError> { + force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none(); + // add together the bindings of the base material and the user material let UnpreparedBindGroup { mut bindings, data: base_data, - } = B::unprepared_bind_group(&self.base, layout, render_device, base_param, true)?; - let extended_bindgroup = - E::unprepared_bind_group(&self.extension, layout, render_device, extended_param, true)?; + } = B::unprepared_bind_group( + &self.base, + layout, + render_device, + base_param, + force_non_bindless, + )?; + let extended_bindgroup = E::unprepared_bind_group( + &self.extension, + layout, + render_device, + extended_param, + force_non_bindless, + )?; bindings.extend(extended_bindgroup.bindings.0); @@ -188,23 +217,72 @@ impl AsBindGroup for ExtendedMaterial { fn bind_group_layout_entries( render_device: &RenderDevice, - _: bool, - ) -> Vec + mut force_non_bindless: bool, + ) -> Vec where Self: Sized, { - // add together the bindings of the standard material and the user material - let mut entries = B::bind_group_layout_entries(render_device, true); - entries.extend(E::bind_group_layout_entries(render_device, true)); + force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none(); + + // Add together the bindings of the standard material and the user + // material, skipping duplicate bindings. Duplicate bindings will occur + // when bindless mode is on, because of the common bindless resource + // arrays, and we need to eliminate the duplicates or `wgpu` will + // complain. + let mut entries = vec![]; + let mut seen_bindings = HashSet::<_>::with_hasher(FixedHasher); + for entry in B::bind_group_layout_entries(render_device, force_non_bindless) + .into_iter() + .chain(E::bind_group_layout_entries(render_device, force_non_bindless).into_iter()) + { + if seen_bindings.insert(entry.binding) { + entries.push(entry); + } + } entries } fn bindless_descriptor() -> Option { - if B::bindless_descriptor().is_some() && E::bindless_descriptor().is_some() { - panic!("Bindless extended materials are currently unsupported") + // We're going to combine the two bindless descriptors. + let base_bindless_descriptor = B::bindless_descriptor()?; + let extended_bindless_descriptor = E::bindless_descriptor()?; + + // Combining the buffers and index tables is straightforward. + + let mut buffers = base_bindless_descriptor.buffers.to_vec(); + let mut index_tables = base_bindless_descriptor.index_tables.to_vec(); + + buffers.extend(extended_bindless_descriptor.buffers.iter().cloned()); + index_tables.extend(extended_bindless_descriptor.index_tables.iter().cloned()); + + // Combining the resources is a little trickier because the resource + // array is indexed by bindless index, so we have to merge the two + // arrays, not just concatenate them. + let max_bindless_index = base_bindless_descriptor + .resources + .len() + .max(extended_bindless_descriptor.resources.len()); + let mut resources = Vec::with_capacity(max_bindless_index); + for bindless_index in 0..max_bindless_index { + // In the event of a conflicting bindless index, we choose the + // base's binding. + match base_bindless_descriptor.resources.get(bindless_index) { + None | Some(&BindlessResourceType::None) => resources.push( + extended_bindless_descriptor + .resources + .get(bindless_index) + .copied() + .unwrap_or(BindlessResourceType::None), + ), + Some(&resource_type) => resources.push(resource_type), + } } - None + Some(BindlessDescriptor { + resources: Cow::Owned(resources), + buffers: Cow::Owned(buffers), + index_tables: Cow::Owned(index_tables), + }) } } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index fdfcdc7b48..12785f3e78 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -124,58 +124,31 @@ pub mod graph { use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr}; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, AssetApp, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetPath, Assets, Handle}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_render::{ alpha::AlphaMode, - camera::{sort_cameras, CameraUpdateSystem, Projection}, + camera::{sort_cameras, CameraUpdateSystems, Projection}, extract_component::ExtractComponentPlugin, extract_resource::ExtractResourcePlugin, + load_shader_library, render_graph::RenderGraph, - render_resource::Shader, + render_resource::{Shader, ShaderRef}, sync_component::SyncComponentPlugin, view::VisibilitySystems, - ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSet, + ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSystems, }; -use bevy_transform::TransformSystem; +use bevy_transform::TransformSystems; + +use std::path::PathBuf; + +fn shader_ref(path: PathBuf) -> ShaderRef { + ShaderRef::Path(AssetPath::from_path_buf(path).with_source("embedded")) +} -pub const PBR_TYPES_SHADER_HANDLE: Handle = - weak_handle!("b0330585-2335-4268-9032-a6c4c2d932f6"); -pub const PBR_BINDINGS_SHADER_HANDLE: Handle = - weak_handle!("13834c18-c7ec-4c4b-bbbd-432c3ba4cace"); -pub const UTILS_HANDLE: Handle = weak_handle!("0a32978f-2744-4608-98b6-4c3000a0638d"); -pub const CLUSTERED_FORWARD_HANDLE: Handle = - weak_handle!("f8e3b4c6-60b7-4b23-8b2e-a6b27bb4ddce"); -pub const PBR_LIGHTING_HANDLE: Handle = - weak_handle!("de0cf697-2876-49a0-aa0f-f015216f70c2"); -pub const PBR_TRANSMISSION_HANDLE: Handle = - weak_handle!("22482185-36bb-4c16-9b93-a20e6d4a2725"); -pub const SHADOWS_HANDLE: Handle = weak_handle!("ff758c5a-3927-4a15-94c3-3fbdfc362590"); -pub const SHADOW_SAMPLING_HANDLE: Handle = - weak_handle!("f6bf5843-54bc-4e39-bd9d-56bfcd77b033"); -pub const PBR_FRAGMENT_HANDLE: Handle = - weak_handle!("1bd3c10d-851b-400c-934a-db489d99cc50"); -pub const PBR_SHADER_HANDLE: Handle = weak_handle!("0eba65ed-3e5b-4752-93ed-e8097e7b0c84"); -pub const PBR_PREPASS_SHADER_HANDLE: Handle = - weak_handle!("9afeaeab-7c45-43ce-b322-4b97799eaeb9"); -pub const PBR_FUNCTIONS_HANDLE: Handle = - weak_handle!("815b8618-f557-4a96-91a5-a2fb7e249fb0"); -pub const PBR_AMBIENT_HANDLE: Handle = weak_handle!("4a90b95b-112a-4a10-9145-7590d6f14260"); -pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle = - weak_handle!("6cf57d9f-222a-429a-bba4-55ba9586e1d4"); -pub const VIEW_TRANSFORMATIONS_SHADER_HANDLE: Handle = - weak_handle!("ec047703-cde3-4876-94df-fed121544abb"); -pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle = - weak_handle!("77b1bd3a-877c-4b2c-981b-b9c68d1b774a"); -pub const PBR_DEFERRED_TYPES_HANDLE: Handle = - weak_handle!("43060da7-a717-4240-80a8-dbddd92bd25d"); -pub const PBR_DEFERRED_FUNCTIONS_HANDLE: Handle = - weak_handle!("9dc46746-c51d-45e3-a321-6a50c3963420"); -pub const RGB9E5_FUNCTIONS_HANDLE: Handle = - weak_handle!("90c19aa3-6a11-4252-8586-d9299352e94f"); const MESHLET_VISIBILITY_BUFFER_RESOLVE_SHADER_HANDLE: Handle = weak_handle!("69187376-3dea-4d0f-b3f5-185bde63d6a2"); @@ -211,110 +184,26 @@ impl Default for PbrPlugin { impl Plugin for PbrPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - PBR_TYPES_SHADER_HANDLE, - "render/pbr_types.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_BINDINGS_SHADER_HANDLE, - "render/pbr_bindings.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, UTILS_HANDLE, "render/utils.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - CLUSTERED_FORWARD_HANDLE, - "render/clustered_forward.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_LIGHTING_HANDLE, - "render/pbr_lighting.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_TRANSMISSION_HANDLE, - "render/pbr_transmission.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - SHADOWS_HANDLE, - "render/shadows.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_DEFERRED_TYPES_HANDLE, - "deferred/pbr_deferred_types.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_DEFERRED_FUNCTIONS_HANDLE, - "deferred/pbr_deferred_functions.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - SHADOW_SAMPLING_HANDLE, - "render/shadow_sampling.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_FUNCTIONS_HANDLE, - "render/pbr_functions.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - RGB9E5_FUNCTIONS_HANDLE, - "render/rgb9e5.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_AMBIENT_HANDLE, - "render/pbr_ambient.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_FRAGMENT_HANDLE, - "render/pbr_fragment.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - PBR_PREPASS_FUNCTIONS_SHADER_HANDLE, - "render/pbr_prepass_functions.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PBR_PREPASS_SHADER_HANDLE, - "render/pbr_prepass.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - PARALLAX_MAPPING_SHADER_HANDLE, - "render/parallax_mapping.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - VIEW_TRANSFORMATIONS_SHADER_HANDLE, - "render/view_transformations.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "render/pbr_types.wgsl"); + load_shader_library!(app, "render/pbr_bindings.wgsl"); + load_shader_library!(app, "render/utils.wgsl"); + load_shader_library!(app, "render/clustered_forward.wgsl"); + load_shader_library!(app, "render/pbr_lighting.wgsl"); + load_shader_library!(app, "render/pbr_transmission.wgsl"); + load_shader_library!(app, "render/shadows.wgsl"); + load_shader_library!(app, "deferred/pbr_deferred_types.wgsl"); + load_shader_library!(app, "deferred/pbr_deferred_functions.wgsl"); + load_shader_library!(app, "render/shadow_sampling.wgsl"); + load_shader_library!(app, "render/pbr_functions.wgsl"); + load_shader_library!(app, "render/rgb9e5.wgsl"); + load_shader_library!(app, "render/pbr_ambient.wgsl"); + load_shader_library!(app, "render/pbr_fragment.wgsl"); + load_shader_library!(app, "render/pbr.wgsl"); + load_shader_library!(app, "render/pbr_prepass_functions.wgsl"); + load_shader_library!(app, "render/pbr_prepass.wgsl"); + load_shader_library!(app, "render/parallax_mapping.wgsl"); + load_shader_library!(app, "render/view_transformations.wgsl"); + // Setup dummy shaders for when MeshletPlugin is not used to prevent shader import errors. load_internal_asset!( app, @@ -401,21 +290,21 @@ impl Plugin for PbrPlugin { ( add_clusters .in_set(SimulationLightSystems::AddClusters) - .after(CameraUpdateSystem), + .after(CameraUpdateSystems), assign_objects_to_clusters .in_set(SimulationLightSystems::AssignLightsToClusters) - .after(TransformSystem::TransformPropagate) + .after(TransformSystems::Propagate) .after(VisibilitySystems::CheckVisibility) - .after(CameraUpdateSystem), + .after(CameraUpdateSystems), clear_directional_light_cascades .in_set(SimulationLightSystems::UpdateDirectionalLightCascades) - .after(TransformSystem::TransformPropagate) - .after(CameraUpdateSystem), + .after(TransformSystems::Propagate) + .after(CameraUpdateSystems), update_directional_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) // This must run after CheckVisibility because it relies on `ViewVisibility` .after(VisibilitySystems::CheckVisibility) - .after(TransformSystem::TransformPropagate) + .after(TransformSystems::Propagate) .after(SimulationLightSystems::UpdateDirectionalLightCascades) // We assume that no entity will be both a directional light and a spot light, // so these systems will run independently of one another. @@ -423,11 +312,11 @@ impl Plugin for PbrPlugin { .ambiguous_with(update_spot_light_frusta), update_point_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) - .after(TransformSystem::TransformPropagate) + .after(TransformSystems::Propagate) .after(SimulationLightSystems::AssignLightsToClusters), update_spot_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) - .after(TransformSystem::TransformPropagate) + .after(TransformSystems::Propagate) .after(SimulationLightSystems::AssignLightsToClusters), ( check_dir_light_mesh_visibility, @@ -435,7 +324,7 @@ impl Plugin for PbrPlugin { ) .in_set(SimulationLightSystems::CheckLightVisibility) .after(VisibilitySystems::CalculateBounds) - .after(TransformSystem::TransformPropagate) + .after(TransformSystems::Propagate) .after(SimulationLightSystems::UpdateLightFrusta) // NOTE: This MUST be scheduled AFTER the core renderer visibility check // because that resets entity `ViewVisibility` for the first view @@ -466,14 +355,21 @@ impl Plugin for PbrPlugin { // Extract the required data from the main world render_app - .add_systems(ExtractSchedule, (extract_clusters, extract_lights)) + .add_systems( + ExtractSchedule, + ( + extract_clusters, + extract_lights, + late_sweep_material_instances, + ), + ) .add_systems( Render, ( prepare_lights - .in_set(RenderSet::ManageViews) + .in_set(RenderSystems::ManageViews) .after(sort_cameras), - prepare_clusters.in_set(RenderSet::PrepareResources), + prepare_clusters.in_set(RenderSystems::PrepareResources), ), ) .init_resource::() diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_pbr/src/light/directional_light.rs index b04a17bf0b..a5798fdde7 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_pbr/src/light/directional_light.rs @@ -41,14 +41,7 @@ use super::*; /// To modify the cascade setup, such as the number of cascades or the maximum shadow distance, /// change the [`CascadeShadowConfig`] component of the entity with the [`DirectionalLight`]. /// -/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource: -/// -/// ``` -/// # use bevy_app::prelude::*; -/// # use bevy_pbr::DirectionalLightShadowMap; -/// App::new() -/// .insert_resource(DirectionalLightShadowMap { size: 2048 }); -/// ``` +/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource. #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component, Default, Debug, Clone)] #[require( diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 3f57464e21..4ff4662bb2 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -1,7 +1,7 @@ use core::ops::DerefMut; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, + entity::{EntityHashMap, EntityHashSet}, prelude::*, }; use bevy_math::{ops, Mat4, Vec3A, Vec4}; @@ -91,9 +91,20 @@ pub mod light_consts { } } +/// Controls the resolution of [`PointLight`] shadow maps. +/// +/// ``` +/// # use bevy_app::prelude::*; +/// # use bevy_pbr::PointLightShadowMap; +/// App::new() +/// .insert_resource(PointLightShadowMap { size: 2048 }); +/// ``` #[derive(Resource, Clone, Debug, Reflect)] #[reflect(Resource, Debug, Default, Clone)] pub struct PointLightShadowMap { + /// The width and height of each of the 6 faces of the cubemap. + /// + /// Defaults to `1024`. pub size: usize, } @@ -108,9 +119,19 @@ impl Default for PointLightShadowMap { pub type WithLight = Or<(With, With, With)>; /// Controls the resolution of [`DirectionalLight`] shadow maps. +/// +/// ``` +/// # use bevy_app::prelude::*; +/// # use bevy_pbr::DirectionalLightShadowMap; +/// App::new() +/// .insert_resource(DirectionalLightShadowMap { size: 4096 }); +/// ``` #[derive(Resource, Clone, Debug, Reflect)] #[reflect(Resource, Debug, Default, Clone)] pub struct DirectionalLightShadowMap { + // The width and height of each cascade. + /// + /// Defaults to `2048`. pub size: usize, } @@ -279,22 +300,22 @@ impl From for CascadeShadowConfig { #[reflect(Component, Debug, Default, Clone)] pub struct Cascades { /// Map from a view to the configuration of each of its [`Cascade`]s. - pub(crate) cascades: EntityHashMap>, + pub cascades: EntityHashMap>, } #[derive(Clone, Debug, Default, Reflect)] #[reflect(Clone, Default)] pub struct Cascade { /// The transform of the light, i.e. the view to world matrix. - pub(crate) world_from_cascade: Mat4, + pub world_from_cascade: Mat4, /// The orthographic projection for this cascade. - pub(crate) clip_from_cascade: Mat4, + pub clip_from_cascade: Mat4, /// The view-projection matrix for this cascade, converting world space into light clip space. /// Importantly, this is derived and stored separately from `view_transform` and `projection` to /// ensure shadow stability. - pub(crate) clip_from_world: Mat4, + pub clip_from_world: Mat4, /// Size of each shadow map texel in world units. - pub(crate) texel_size: f32, + pub texel_size: f32, } pub fn clear_directional_light_cascades(mut lights: Query<(&DirectionalLight, &mut Cascades)>) { @@ -492,8 +513,7 @@ pub enum ShadowFilteringMethod { Gaussian, /// A randomized filter that varies over time, good when TAA is in use. /// - /// Good quality when used with - /// [`TemporalAntiAliasing`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasing) + /// Good quality when used with `TemporalAntiAliasing` /// and good performance. /// /// For directional and spot lights, this uses a [method by Jorge Jimenez for @@ -564,9 +584,13 @@ pub fn update_directional_light_frusta( // NOTE: Run this after assign_lights_to_clusters! pub fn update_point_light_frusta( global_lights: Res, - mut views: Query< - (Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta), - Or<(Changed, Changed)>, + mut views: Query<(Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta)>, + changed_lights: Query< + Entity, + ( + With, + Or<(Changed, Changed)>, + ), >, ) { let view_rotations = CUBE_MAP_FACES @@ -575,6 +599,12 @@ pub fn update_point_light_frusta( .collect::>(); for (entity, transform, point_light, mut cubemap_frusta) in &mut views { + // If this light hasn't changed, and neither has the set of global_lights, + // then we can skip this calculation. + if !global_lights.is_changed() && !changed_lights.contains(entity) { + continue; + } + // The frusta are used for culling meshes to the light for shadow mapping // so if shadow mapping is disabled for this light, then the frusta are // not needed. diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_pbr/src/light/point_light.rs index 4f4795fb55..f2e4224d28 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_pbr/src/light/point_light.rs @@ -19,6 +19,12 @@ use super::*; /// | 4000 | 300 | | 75-100 | 40.5 | /// /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting) +/// +/// ## Shadows +/// +/// To enable shadows, set the `shadows_enabled` property to `true`. +/// +/// To control the resolution of the shadow maps, use the [`PointLightShadowMap`] resource. #[derive(Component, Debug, Clone, Copy, Reflect)] #[reflect(Component, Default, Debug, Clone)] #[require( diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 52ccaef432..b31801f757 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -44,7 +44,7 @@ //! //! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments -use bevy_asset::{weak_handle, AssetId, Handle}; +use bevy_asset::{AssetId, Handle}; use bevy_ecs::{ component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read, }; @@ -56,8 +56,8 @@ use bevy_render::{ render_asset::RenderAssets, render_resource::{ binding_types::{self, uniform_buffer}, - BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, ShaderStages, - TextureSampleType, TextureView, + BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType, + TextureView, }, renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, @@ -72,10 +72,6 @@ use crate::{ use super::{LightProbeComponent, RenderViewLightProbes}; -/// A handle to the environment map helper shader. -pub const ENVIRONMENT_MAP_SHADER_HANDLE: Handle = - weak_handle!("d38c4ec4-e84c-468f-b485-bf44745db937"); - /// A pair of cubemap textures that represent the surroundings of a specific /// area in space. /// diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 05dd51c379..431d1245a2 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -137,8 +137,8 @@ use bevy_image::Image; use bevy_render::{ render_asset::RenderAssets, render_resource::{ - binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader, - TextureSampleType, TextureView, + binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, TextureSampleType, + TextureView, }, renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, @@ -146,7 +146,7 @@ use bevy_render::{ use bevy_utils::default; use core::{num::NonZero, ops::Deref}; -use bevy_asset::{weak_handle, AssetId, Handle}; +use bevy_asset::{AssetId, Handle}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use crate::{ @@ -156,9 +156,6 @@ use crate::{ use super::LightProbeComponent; -pub const IRRADIANCE_VOLUME_SHADER_HANDLE: Handle = - weak_handle!("7fc7dcd8-3f90-4124-b093-be0e53e08205"); - /// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can /// overflow the number of texture bindings when deferred rendering is in use /// (see issue #11885). diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 2779209da1..74710ce1d5 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -1,7 +1,7 @@ //! Light probes for baked global illumination. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; +use bevy_asset::AssetId; use bevy_core_pipeline::core_3d::Camera3d; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -15,37 +15,30 @@ use bevy_ecs::{ }; use bevy_image::Image; use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_instances::ExtractInstancesPlugin, + load_shader_library, primitives::{Aabb, Frustum}, render_asset::RenderAssets, - render_resource::{DynamicUniformBuffer, Sampler, Shader, ShaderType, TextureView}, + render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, settings::WgpuFeatures, sync_world::RenderEntity, texture::{FallbackImage, GpuImage}, view::{ExtractedView, Visibility}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::{components::Transform, prelude::GlobalTransform}; use tracing::error; use core::{hash::Hash, ops::Deref}; -use crate::{ - irradiance_volume::IRRADIANCE_VOLUME_SHADER_HANDLE, - light_probe::environment_map::{ - EnvironmentMapIds, EnvironmentMapLight, ENVIRONMENT_MAP_SHADER_HANDLE, - }, -}; +use crate::light_probe::environment_map::{EnvironmentMapIds, EnvironmentMapLight}; use self::irradiance_volume::IrradianceVolume; -pub const LIGHT_PROBE_SHADER_HANDLE: Handle = - weak_handle!("e80a2ae6-1c5a-4d9a-a852-d66ff0e6bf7f"); - pub mod environment_map; pub mod irradiance_volume; @@ -344,24 +337,9 @@ pub struct ViewEnvironmentMapUniformOffset(u32); impl Plugin for LightProbePlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - LIGHT_PROBE_SHADER_HANDLE, - "light_probe.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - ENVIRONMENT_MAP_SHADER_HANDLE, - "environment_map.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - IRRADIANCE_VOLUME_SHADER_HANDLE, - "irradiance_volume.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "light_probe.wgsl"); + load_shader_library!(app, "environment_map.wgsl"); + load_shader_library!(app, "irradiance_volume.wgsl"); app.register_type::() .register_type::() @@ -383,7 +361,7 @@ impl Plugin for LightProbePlugin { .add_systems( Render, (upload_light_probes, prepare_environment_uniform_buffer) - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), ); } } diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index 741085fd9f..cb7aecc2cb 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -32,7 +32,7 @@ //! [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; +use bevy_asset::{AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -47,11 +47,12 @@ use bevy_ecs::{ }; use bevy_image::Image; use bevy_math::{uvec2, vec4, Rect, UVec2}; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ + load_shader_library, render_asset::RenderAssets, - render_resource::{Sampler, Shader, TextureView, WgpuSampler, WgpuTextureView}, + render_resource::{Sampler, TextureView, WgpuSampler, WgpuTextureView}, renderer::RenderAdapter, sync_world::MainEntity, texture::{FallbackImage, GpuImage}, @@ -64,11 +65,7 @@ use fixedbitset::FixedBitSet; use nonmax::{NonMaxU16, NonMaxU32}; use tracing::error; -use crate::{binding_arrays_are_usable, ExtractMeshesSet}; - -/// The ID of the lightmap shader. -pub const LIGHTMAP_SHADER_HANDLE: Handle = - weak_handle!("fc28203f-f258-47f3-973c-ce7d1dd70e59"); +use crate::{binding_arrays_are_usable, MeshExtractionSystems}; /// The number of lightmaps that we store in a single slab, if bindless textures /// are in use. @@ -188,12 +185,7 @@ pub struct LightmapSlotIndex(pub(crate) NonMaxU16); impl Plugin for LightmapPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - LIGHTMAP_SHADER_HANDLE, - "lightmap.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "lightmap.wgsl"); } fn finish(&self, app: &mut App) { @@ -201,9 +193,10 @@ impl Plugin for LightmapPlugin { return; }; - render_app - .init_resource::() - .add_systems(ExtractSchedule, extract_lightmaps.after(ExtractMeshesSet)); + render_app.init_resource::().add_systems( + ExtractSchedule, + extract_lightmaps.after(MeshExtractionSystems), + ); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 1ac3930a0a..af11db1ba6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -8,7 +8,7 @@ use crate::meshlet::{ }; use crate::*; use bevy_asset::prelude::AssetChanged; -use bevy_asset::{Asset, AssetEvents, AssetId, AssetServer, UntypedAssetId}; +use bevy_asset::{Asset, AssetEventSystems, AssetId, AssetServer, UntypedAssetId}; use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; use bevy_core_pipeline::{ @@ -29,11 +29,12 @@ use bevy_ecs::{ SystemParamItem, }, }; -use bevy_platform_support::collections::hash_map::Entry; -use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_platform_support::hash::FixedHasher; +use bevy_platform::collections::hash_map::Entry; +use bevy_platform::collections::{HashMap, HashSet}; +use bevy_platform::hash::FixedHasher; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::camera::extract_cameras; use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed; use bevy_render::render_asset::prepare_assets; use bevy_render::renderer::RenderQueue; @@ -51,6 +52,7 @@ use bevy_render::{ }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; +use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; use tracing::error; @@ -286,7 +288,7 @@ where PostUpdate, ( mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), - check_entities_needing_specialization::.after(AssetEvents), + check_entities_needing_specialization::.after(AssetEventSystems), ) .after(mark_3d_meshes_as_changed_if_their_assets_changed), ); @@ -304,7 +306,7 @@ where .init_resource::>() .init_resource::>() .init_resource::>() - .init_resource::>() + .init_resource::() .add_render_command::>() .add_render_command::>() .add_render_command::>() @@ -314,20 +316,24 @@ where .add_systems( ExtractSchedule, ( - extract_mesh_materials::.before(ExtractMeshesSet), - extract_entities_needs_specialization::, + extract_mesh_materials::.in_set(MaterialExtractionSystems), + early_sweep_material_instances:: + .after(MaterialExtractionSystems) + .before(late_sweep_material_instances), + extract_entities_needs_specialization::.after(extract_cameras), ), ) .add_systems( Render, ( specialize_material_meshes:: - .in_set(RenderSet::PrepareMeshes) + .in_set(RenderSystems::PrepareMeshes) .after(prepare_assets::>) .after(prepare_assets::) - .after(collect_meshes_for_gpu_building), + .after(collect_meshes_for_gpu_building) + .after(set_mesh_motion_vector_flags), queue_material_meshes:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>), ), ) @@ -338,7 +344,7 @@ where write_material_bind_group_buffers::, ) .chain() - .in_set(RenderSet::PrepareBindGroups) + .in_set(RenderSystems::PrepareBindGroups) .after(prepare_assets::>), ); @@ -350,12 +356,15 @@ where .add_systems( Render, ( - check_views_lights_need_specialization.in_set(RenderSet::PrepareAssets), + check_views_lights_need_specialization + .in_set(RenderSystems::PrepareAssets), + // specialize_shadows:: also needs to run after prepare_assets::>, + // which is fine since ManageViews is after PrepareAssets specialize_shadows:: - .in_set(RenderSet::PrepareMeshes) - .after(prepare_assets::>), + .in_set(RenderSystems::ManageViews) + .after(prepare_lights), queue_shadows:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>), ), ); @@ -365,7 +374,7 @@ where render_app.add_systems( Render, queue_material_meshlet_meshes:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .run_if(resource_exists::), ); @@ -373,7 +382,7 @@ where render_app.add_systems( Render, prepare_material_meshlet_meshes_main_opaque_pass:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>) .before(queue_material_meshlet_meshes::) .run_if(resource_exists::), @@ -399,6 +408,14 @@ where } } +/// A dummy [`AssetId`] that we use as a placeholder whenever a mesh doesn't +/// have a material. +/// +/// See the comments in [`RenderMaterialInstances::mesh_material`] for more +/// information. +pub(crate) static DUMMY_MESH_MATERIAL: AssetId = + AssetId::::invalid(); + /// A key uniquely identifying a specialized [`MaterialPipeline`]. pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, @@ -537,7 +554,7 @@ pub struct SetMaterialBindGroup(PhantomData); impl RenderCommand

for SetMaterialBindGroup { type Param = ( SRes>>, - SRes>, + SRes, SRes>, ); type ViewQuery = (); @@ -559,10 +576,13 @@ impl RenderCommand

for SetMaterial let material_instances = material_instances.into_inner(); let material_bind_group_allocator = material_bind_group_allocator.into_inner(); - let Some(material_asset_id) = material_instances.get(&item.main_entity()) else { + let Some(material_instance) = material_instances.instances.get(&item.main_entity()) else { return RenderCommandResult::Skip; }; - let Some(material) = materials.get(*material_asset_id) else { + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { + return RenderCommandResult::Skip; + }; + let Some(material) = materials.get(material_asset_id) else { return RenderCommandResult::Skip; }; let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group) @@ -577,16 +597,53 @@ impl RenderCommand

for SetMaterial } } -/// Stores all extracted instances of a [`Material`] in the render world. -#[derive(Resource, Deref, DerefMut)] -pub struct RenderMaterialInstances(pub MainEntityHashMap>); +/// Stores all extracted instances of all [`Material`]s in the render world. +#[derive(Resource, Default)] +pub struct RenderMaterialInstances { + /// Maps from each entity in the main world to the + /// [`RenderMaterialInstance`] associated with it. + pub instances: MainEntityHashMap, + /// A monotonically-increasing counter, which we use to sweep + /// [`RenderMaterialInstances::instances`] when the entities and/or required + /// components are removed. + current_change_tick: Tick, +} -impl Default for RenderMaterialInstances { - fn default() -> Self { - Self(Default::default()) +impl RenderMaterialInstances { + /// Returns the mesh material ID for the entity with the given mesh, or a + /// dummy mesh material ID if the mesh has no material ID. + /// + /// Meshes almost always have materials, but in very specific circumstances + /// involving custom pipelines they won't. (See the + /// `specialized_mesh_pipelines` example.) + pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId { + match self.instances.get(&entity) { + Some(render_instance) => render_instance.asset_id, + None => DUMMY_MESH_MATERIAL.into(), + } } } +/// The material associated with a single mesh instance in the main world. +/// +/// Note that this uses an [`UntypedAssetId`] and isn't generic over the +/// material type, for simplicity. +pub struct RenderMaterialInstance { + /// The material asset. + pub(crate) asset_id: UntypedAssetId, + /// The [`RenderMaterialInstances::current_change_tick`] at which this + /// material instance was last modified. + last_change_tick: Tick, +} + +/// A [`SystemSet`] that contains all `extract_mesh_materials` systems. +#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] +pub struct MaterialExtractionSystems; + +/// Deprecated alias for [`MaterialExtractionSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `MaterialExtractionSystems`.")] +pub type ExtractMaterialsSet = MaterialExtractionSystems; + pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key @@ -643,7 +700,7 @@ pub const fn screen_space_specular_transmission_pipeline_key( /// /// As [`crate::render::mesh::collect_meshes_for_gpu_building`] only considers /// meshes that were newly extracted, and it writes information from the -/// [`RenderMeshMaterialIds`] into the +/// [`RenderMaterialInstances`] into the /// [`crate::render::mesh::MeshInputUniform`], we must tell /// [`crate::render::mesh::extract_meshes_for_gpu_building`] to re-extract a /// mesh if its material changed. Otherwise, the material binding information in @@ -664,51 +721,138 @@ fn mark_meshes_as_changed_if_their_materials_changed( } } -/// Fills the [`RenderMaterialInstances`] and [`RenderMeshMaterialIds`] -/// resources from the meshes in the scene. +/// Fills the [`RenderMaterialInstances`] resources from the meshes in the +/// scene. fn extract_mesh_materials( - mut material_instances: ResMut>, - mut material_ids: ResMut, + mut material_instances: ResMut, changed_meshes_query: Extract< Query< (Entity, &ViewVisibility, &MeshMaterial3d), Or<(Changed, Changed>)>, >, >, - mut removed_visibilities_query: Extract>, - mut removed_materials_query: Extract>>, ) { + let last_change_tick = material_instances.current_change_tick; + for (entity, view_visibility, material) in &changed_meshes_query { if view_visibility.get() { - material_instances.insert(entity.into(), material.id()); - material_ids.insert(entity.into(), material.id().into()); + material_instances.instances.insert( + entity.into(), + RenderMaterialInstance { + asset_id: material.id().untyped(), + last_change_tick, + }, + ); } else { - material_instances.remove(&MainEntity::from(entity)); - material_ids.remove(entity.into()); + material_instances + .instances + .remove(&MainEntity::from(entity)); + } + } +} + +/// Removes mesh materials from [`RenderMaterialInstances`] when their +/// [`MeshMaterial3d`] components are removed. +/// +/// This is tricky because we have to deal with the case in which a material of +/// type A was removed and replaced with a material of type B in the same frame +/// (which is actually somewhat common of an operation). In this case, even +/// though an entry will be present in `RemovedComponents>`, +/// we must not remove the entry in `RenderMaterialInstances` which corresponds +/// to material B. To handle this case, we use change ticks to avoid removing +/// the entry if it was updated this frame. +/// +/// This is the first of two sweep phases. Because this phase runs once per +/// material type, we need a second phase in order to guarantee that we only +/// bump [`RenderMaterialInstances::current_change_tick`] once. +fn early_sweep_material_instances( + mut material_instances: ResMut, + mut removed_materials_query: Extract>>, +) where + M: Material, +{ + let last_change_tick = material_instances.current_change_tick; + + for entity in removed_materials_query.read() { + if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) { + // Only sweep the entry if it wasn't updated this frame. + if occupied_entry.get().last_change_tick != last_change_tick { + occupied_entry.remove(); + } + } + } +} + +/// Removes mesh materials from [`RenderMaterialInstances`] when their +/// [`ViewVisibility`] components are removed. +/// +/// This runs after all invocations of [`early_sweep_material_instances`] and is +/// responsible for bumping [`RenderMaterialInstances::current_change_tick`] in +/// preparation for a new frame. +pub(crate) fn late_sweep_material_instances( + mut material_instances: ResMut, + mut removed_visibilities_query: Extract>, +) { + let last_change_tick = material_instances.current_change_tick; + + for entity in removed_visibilities_query.read() { + if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) { + // Only sweep the entry if it wasn't updated this frame. It's + // possible that a `ViewVisibility` component was removed and + // re-added in the same frame. + if occupied_entry.get().last_change_tick != last_change_tick { + occupied_entry.remove(); + } } } - for entity in removed_visibilities_query - .read() - .chain(removed_materials_query.read()) - { - // Only queue a mesh for removal if we didn't pick it up above. - // It's possible that a necessary component was removed and re-added in - // the same frame. - if !changed_meshes_query.contains(entity) { - material_instances.remove(&MainEntity::from(entity)); - material_ids.remove(entity.into()); - } - } + material_instances + .current_change_tick + .set(last_change_tick.get() + 1); } pub fn extract_entities_needs_specialization( entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, + mut removed_mesh_material_components: Extract>>, + mut specialized_material_pipeline_cache: ResMut>, + mut specialized_prepass_material_pipeline_cache: Option< + ResMut>, + >, + mut specialized_shadow_material_pipeline_cache: Option< + ResMut>, + >, + views: Query<&ExtractedView>, ticks: SystemChangeTick, ) where M: Material, { + // Clean up any despawned entities, we do this first in case the removed material was re-added + // the same frame, thus will appear both in the removed components list and have been added to + // the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter + for entity in removed_mesh_material_components.read() { + entity_specialization_ticks.remove(&MainEntity::from(entity)); + for view in views { + if let Some(cache) = + specialized_material_pipeline_cache.get_mut(&view.retained_view_entity) + { + cache.remove(&MainEntity::from(entity)); + } + if let Some(cache) = specialized_prepass_material_pipeline_cache + .as_mut() + .and_then(|c| c.get_mut(&view.retained_view_entity)) + { + cache.remove(&MainEntity::from(entity)); + } + if let Some(cache) = specialized_shadow_material_pipeline_cache + .as_mut() + .and_then(|c| c.get_mut(&view.retained_view_entity)) + { + cache.remove(&MainEntity::from(entity)); + } + } + } + for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); @@ -787,28 +931,35 @@ impl Default for SpecializedMaterialViewPipelineCache { pub fn check_entities_needing_specialization( needs_specialization: Query< Entity, - Or<( - Changed, - AssetChanged, - Changed>, - AssetChanged>, - )>, + ( + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + With>, + ), >, + mut par_local: Local>>, mut entities_needing_specialization: ResMut>, ) where M: Material, { entities_needing_specialization.clear(); - for entity in &needs_specialization { - entities_needing_specialization.push(entity); - } + + needs_specialization + .par_iter() + .for_each(|entity| par_local.borrow_local_mut().push(entity)); + + par_local.drain_into(&mut entities_needing_specialization); } pub fn specialize_material_meshes( render_meshes: Res>, render_materials: Res>>, render_mesh_instances: Res, - render_material_instances: Res>, + render_material_instances: Res, render_lightmaps: Res, render_visibility_ranges: Res, ( @@ -863,6 +1014,17 @@ pub fn specialize_material_meshes( .or_default(); for (_, visible_entity) in visible_entities.iter::() { + let Some(material_instance) = render_material_instances.instances.get(visible_entity) + else { + continue; + }; + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(visible_entity) @@ -874,17 +1036,10 @@ pub fn specialize_material_meshes( if !needs_specialization { continue; } - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { - continue; - }; - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) - else { - continue; - }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let Some(material) = render_materials.get(*material_asset_id) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; let Some(material_bind_group) = @@ -960,7 +1115,7 @@ pub fn specialize_material_meshes( pub fn queue_material_meshes( render_materials: Res>>, render_mesh_instances: Res, - render_material_instances: Res>, + render_material_instances: Res, mesh_allocator: Res, gpu_preprocessing_support: Res, mut opaque_render_phases: ResMut>, @@ -1010,14 +1165,18 @@ pub fn queue_material_meshes( continue; } - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + let Some(material_instance) = render_material_instances.instances.get(visible_entity) + else { + continue; + }; + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { continue; }; let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; }; - let Some(material) = render_materials.get(*material_asset_id) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index 442050d9b2..b539d2098f 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -4,23 +4,23 @@ //! allocator manages each bind group, assigning slots to materials as //! appropriate. -use core::{iter, marker::PhantomData, mem}; +use core::{cmp::Ordering, iter, marker::PhantomData, mem, ops::Range}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ resource::Resource, world::{FromWorld, World}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ render_resource::{ BindGroup, BindGroupEntry, BindGroupLayout, BindingNumber, BindingResource, - BindingResources, BindlessDescriptor, BindlessIndex, BindlessResourceType, Buffer, - BufferBinding, BufferDescriptor, BufferId, BufferInitDescriptor, BufferUsages, - CompareFunction, FilterMode, OwnedBindingResource, PreparedBindGroup, RawBufferVec, - Sampler, SamplerDescriptor, SamplerId, TextureView, TextureViewDimension, TextureViewId, - UnpreparedBindGroup, WgpuSampler, WgpuTextureView, + BindingResources, BindlessDescriptor, BindlessIndex, BindlessIndexTableDescriptor, + BindlessResourceType, Buffer, BufferBinding, BufferDescriptor, BufferId, + BufferInitDescriptor, BufferUsages, CompareFunction, FilterMode, OwnedBindingResource, + PreparedBindGroup, RawBufferVec, Sampler, SamplerDescriptor, SamplerId, TextureView, + TextureViewDimension, TextureViewId, UnpreparedBindGroup, WgpuSampler, WgpuTextureView, }, renderer::{RenderDevice, RenderQueue}, settings::WgpuFeatures, @@ -89,11 +89,16 @@ where /// regenerated. bind_group: Option, - /// A GPU-accessible buffer that holds the mapping from binding index to + /// The GPU-accessible buffers that hold the mapping from binding index to /// bindless slot. /// - /// This is conventionally assigned to bind group binding 0. - bindless_index_table: MaterialBindlessIndexTable, + /// This is conventionally assigned to bind group binding 0, but it can be + /// changed using the `#[bindless(index_table(binding(B)))]` attribute on + /// `AsBindGroup`. + /// + /// Because the slab binary searches this table, the entries within must be + /// sorted by bindless index. + bindless_index_tables: Vec>, /// The binding arrays containing samplers. samplers: HashMap>, @@ -122,13 +127,25 @@ where /// A GPU-accessible buffer that holds the mapping from binding index to /// bindless slot. /// -/// This is conventionally assigned to bind group binding 0. +/// This is conventionally assigned to bind group binding 0, but it can be +/// changed by altering the [`Self::binding_number`], which corresponds to the +/// `#[bindless(index_table(binding(B)))]` attribute in `AsBindGroup`. struct MaterialBindlessIndexTable where M: Material, { /// The buffer containing the mappings. buffer: RetainedRawBufferVec, + /// The range of bindless indices that this bindless index table covers. + /// + /// If this range is M..N, then the field at index $i$ maps to bindless + /// index $i$ + M. The size of this table is N - M. + /// + /// This corresponds to the `#[bindless(index_table(range(M..N)))]` + /// attribute in `AsBindGroup`. + index_range: Range, + /// The binding number that this index table is assigned to in the shader. + binding_number: BindingNumber, phantom: PhantomData, } @@ -601,29 +618,54 @@ where M: Material, { /// Creates a new [`MaterialBindlessIndexTable`] for a single slab. - fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessIndexTable { + fn new( + bindless_index_table_descriptor: &BindlessIndexTableDescriptor, + ) -> MaterialBindlessIndexTable { // Preallocate space for one bindings table, so that there will always be a buffer. let mut buffer = RetainedRawBufferVec::new(BufferUsages::STORAGE); - for _ in 0..bindless_descriptor.resources.len() { + for _ in *bindless_index_table_descriptor.indices.start + ..*bindless_index_table_descriptor.indices.end + { buffer.push(0); } MaterialBindlessIndexTable { buffer, + index_range: bindless_index_table_descriptor.indices.clone(), + binding_number: bindless_index_table_descriptor.binding_number, phantom: PhantomData, } } - /// Returns the binding index table for a single material. + /// Returns the bindings in the binding index table. /// - /// Element *i* of the returned binding index table contains the slot of the - /// bindless resource with bindless index *i*. - fn get(&self, slot: MaterialBindGroupSlot, bindless_descriptor: &BindlessDescriptor) -> &[u32] { - let struct_size = bindless_descriptor.resources.len(); + /// If the current [`MaterialBindlessIndexTable::index_range`] is M..N, then + /// element *i* of the returned binding index table contains the slot of the + /// bindless resource with bindless index *i* + M. + fn get(&self, slot: MaterialBindGroupSlot) -> &[u32] { + let struct_size = *self.index_range.end as usize - *self.index_range.start as usize; let start = struct_size * slot.0 as usize; &self.buffer.values()[start..(start + struct_size)] } + /// Returns a single binding from the binding index table. + fn get_binding( + &self, + slot: MaterialBindGroupSlot, + bindless_index: BindlessIndex, + ) -> Option { + if bindless_index < self.index_range.start || bindless_index >= self.index_range.end { + return None; + } + self.get(slot) + .get((*bindless_index - *self.index_range.start) as usize) + .copied() + } + + fn table_length(&self) -> u32 { + self.index_range.end.0 - self.index_range.start.0 + } + /// Updates the binding index table for a single material. /// /// The `allocated_resource_slots` map contains a mapping from the @@ -635,22 +677,37 @@ where &mut self, slot: MaterialBindGroupSlot, allocated_resource_slots: &HashMap, - bindless_descriptor: &BindlessDescriptor, ) { - let table_len = bindless_descriptor.resources.len(); + let table_len = self.table_length() as usize; let range = (slot.0 as usize * table_len)..((slot.0 as usize + 1) * table_len); while self.buffer.len() < range.end { self.buffer.push(0); } for (&bindless_index, &resource_slot) in allocated_resource_slots { - self.buffer - .set(*bindless_index + range.start as u32, resource_slot); + if self.index_range.contains(&bindless_index) { + self.buffer.set( + *bindless_index + range.start as u32 - *self.index_range.start, + resource_slot, + ); + } } // Mark the buffer as needing to be recreated, in case we grew it. self.buffer.dirty = BufferDirtyState::NeedsReserve; } + + /// Returns the [`BindGroupEntry`] for the index table itself. + fn bind_group_entry(&self) -> BindGroupEntry { + BindGroupEntry { + binding: *self.binding_number, + resource: self + .buffer + .buffer() + .expect("Bindings buffer must exist") + .as_entire_binding(), + } + } } impl RetainedRawBufferVec @@ -743,11 +800,7 @@ where ) -> MaterialBindingId { for (slab_index, slab) in self.slabs.iter_mut().enumerate() { trace!("Trying to allocate in slab {}", slab_index); - match slab.try_allocate( - unprepared_bind_group, - &self.bindless_descriptor, - self.slab_capacity, - ) { + match slab.try_allocate(unprepared_bind_group, self.slab_capacity) { Ok(slot) => { return MaterialBindingId { group: MaterialBindGroupIndex(slab_index as u32), @@ -767,11 +820,7 @@ where .slabs .last_mut() .expect("We just pushed a slab") - .try_allocate( - unprepared_bind_group, - &self.bindless_descriptor, - self.slab_capacity, - ) + .try_allocate(unprepared_bind_group, self.slab_capacity) else { panic!("An allocation into an empty slab should always succeed") }; @@ -852,7 +901,6 @@ where fn try_allocate( &mut self, unprepared_bind_group: UnpreparedBindGroup, - bindless_descriptor: &BindlessDescriptor, slot_capacity: u32, ) -> Result> { // Locate pre-existing resources, and determine how many free slots we need. @@ -890,8 +938,9 @@ where self.insert_resources(unprepared_bind_group.bindings, allocation_candidate); // Serialize the allocated resource slots. - self.bindless_index_table - .set(slot, &allocated_resource_slots, bindless_descriptor); + for bindless_index_table in &mut self.bindless_index_tables { + bindless_index_table.set(slot, &allocated_resource_slots); + } // Insert extra data. if self.extra_data.len() < (*slot as usize + 1) { @@ -1103,13 +1152,17 @@ where /// descriptor, from this slab. fn free(&mut self, slot: MaterialBindGroupSlot, bindless_descriptor: &BindlessDescriptor) { // Loop through each binding. - for (bindless_index, (bindless_resource_type, &bindless_binding)) in bindless_descriptor - .resources - .iter() - .zip(self.bindless_index_table.get(slot, bindless_descriptor)) - .enumerate() + for (bindless_index, bindless_resource_type) in + bindless_descriptor.resources.iter().enumerate() { let bindless_index = BindlessIndex::from(bindless_index as u32); + let Some(bindless_index_table) = self.get_bindless_index_table(bindless_index) else { + continue; + }; + let Some(bindless_binding) = bindless_index_table.get_binding(slot, bindless_index) + else { + continue; + }; // Free the binding. If the resource in question was anything other // than a data buffer, then it has a reference count and @@ -1176,8 +1229,10 @@ where bindless_descriptor: &BindlessDescriptor, slab_capacity: u32, ) { - // Create the bindless index table buffer if needed. - self.bindless_index_table.buffer.prepare(render_device); + // Create the bindless index table buffers if needed. + for bindless_index_table in &mut self.bindless_index_tables { + bindless_index_table.buffer.prepare(render_device); + } // Create any data buffers we were managing if necessary. for data_buffer in self.data_buffers.values_mut() { @@ -1232,15 +1287,11 @@ where required_binding_array_size, ); - let mut bind_group_entries = vec![BindGroupEntry { - binding: 0, - resource: self - .bindless_index_table - .buffer - .buffer() - .expect("Bindings buffer must exist") - .as_entire_binding(), - }]; + let mut bind_group_entries: Vec<_> = self + .bindless_index_tables + .iter() + .map(|bindless_index_table| bindless_index_table.bind_group_entry()) + .collect(); for &(&binding, ref binding_resource_array) in binding_resource_arrays.iter() { bind_group_entries.push(BindGroupEntry { @@ -1283,9 +1334,11 @@ where /// Currently, this consists of the bindless index table plus any data /// buffers we're managing. fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { - self.bindless_index_table - .buffer - .write(render_device, render_queue); + for bindless_index_table in &mut self.bindless_index_tables { + bindless_index_table + .buffer + .write(render_device, render_queue); + } for data_buffer in self.data_buffers.values_mut() { data_buffer.buffer.write(render_device, render_queue); @@ -1520,6 +1573,26 @@ where .and_then(|data| data.as_ref()) .expect("Extra data not present") } + + /// Returns the bindless index table containing the given bindless index. + fn get_bindless_index_table( + &self, + bindless_index: BindlessIndex, + ) -> Option<&MaterialBindlessIndexTable> { + let table_index = self + .bindless_index_tables + .binary_search_by(|bindless_index_table| { + if bindless_index < bindless_index_table.index_range.start { + Ordering::Less + } else if bindless_index >= bindless_index_table.index_range.end { + Ordering::Greater + } else { + Ordering::Equal + } + }) + .ok()?; + self.bindless_index_tables.get(table_index) + } } impl MaterialBindlessBindingArray @@ -1708,9 +1781,15 @@ where } } + let bindless_index_tables = bindless_descriptor + .index_tables + .iter() + .map(|bindless_index_table| MaterialBindlessIndexTable::new(bindless_index_table)) + .collect(); + MaterialBindlessSlab { bind_group: None, - bindless_index_table: MaterialBindlessIndexTable::new(bindless_descriptor), + bindless_index_tables, samplers, textures, buffers, diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 016722b096..ed2be52f53 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -3,7 +3,7 @@ use super::asset::{ }; use alloc::borrow::Cow; use bevy_math::{ops::log2, IVec3, Vec2, Vec3, Vec3Swizzles}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::{ mesh::{Indices, Mesh}, render_resource::PrimitiveTopology, diff --git a/crates/bevy_pbr/src/meshlet/instance_manager.rs b/crates/bevy_pbr/src/meshlet/instance_manager.rs index 26f6432a1f..661d4791ae 100644 --- a/crates/bevy_pbr/src/meshlet/instance_manager.rs +++ b/crates/bevy_pbr/src/meshlet/instance_manager.rs @@ -1,18 +1,18 @@ use super::{meshlet_mesh_manager::MeshletMeshManager, MeshletMesh, MeshletMesh3d}; use crate::{ - Material, MeshFlags, MeshTransforms, MeshUniform, NotShadowCaster, NotShadowReceiver, - PreviousGlobalTransform, RenderMaterialBindings, RenderMaterialInstances, - RenderMeshMaterialIds, + material::DUMMY_MESH_MATERIAL, Material, MaterialBindingId, MeshFlags, MeshTransforms, + MeshUniform, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform, + RenderMaterialBindings, RenderMaterialInstances, }; use bevy_asset::{AssetEvent, AssetServer, Assets, UntypedAssetId}; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entities, Entity}, + entity::{Entities, Entity, EntityHashMap}, event::EventReader, query::Has, resource::Resource, system::{Local, Query, Res, ResMut, SystemState}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ render_resource::StorageBuffer, sync_world::MainEntity, view::RenderLayers, MainWorld, }; @@ -90,7 +90,7 @@ impl InstanceManager { transform: &GlobalTransform, previous_transform: Option<&PreviousGlobalTransform>, render_layers: Option<&RenderLayers>, - mesh_material_ids: &RenderMeshMaterialIds, + mesh_material_ids: &RenderMaterialInstances, render_material_bindings: &RenderMaterialBindings, not_shadow_receiver: bool, not_shadow_caster: bool, @@ -113,10 +113,15 @@ impl InstanceManager { }; let mesh_material = mesh_material_ids.mesh_material(instance); - let mesh_material_binding_id = render_material_bindings - .get(&mesh_material) - .cloned() - .unwrap_or_default(); + let mesh_material_binding_id = if mesh_material != DUMMY_MESH_MATERIAL.untyped() { + render_material_bindings + .get(&mesh_material) + .cloned() + .unwrap_or_default() + } else { + // Use a dummy binding ID if the mesh has no material + MaterialBindingId::default() + }; let mesh_uniform = MeshUniform::new( &transforms, @@ -187,7 +192,7 @@ pub fn extract_meshlet_mesh_entities( mut instance_manager: ResMut, // TODO: Replace main_world and system_state when Extract>> is possible mut main_world: ResMut, - mesh_material_ids: Res, + mesh_material_ids: Res, render_material_bindings: Res, mut system_state: Local< Option< @@ -269,20 +274,22 @@ pub fn extract_meshlet_mesh_entities( /// and note that the material is used by at least one entity in the scene. pub fn queue_material_meshlet_meshes( mut instance_manager: ResMut, - render_material_instances: Res>, + render_material_instances: Res, ) { let instance_manager = instance_manager.deref_mut(); for (i, (instance, _, _)) in instance_manager.instances.iter().enumerate() { - if let Some(material_asset_id) = render_material_instances.get(instance) { - if let Some(material_id) = instance_manager - .material_id_lookup - .get(&material_asset_id.untyped()) - { - instance_manager - .material_ids_present_in_scene - .insert(*material_id); - instance_manager.instance_material_ids.get_mut()[i] = *material_id; + if let Some(material_instance) = render_material_instances.instances.get(instance) { + if let Ok(material_asset_id) = material_instance.asset_id.try_typed::() { + if let Some(material_id) = instance_manager + .material_id_lookup + .get(&material_asset_id.untyped()) + { + instance_manager + .material_ids_present_in_scene + .insert(*material_id); + instance_manager.instance_material_ids.get_mut()[i] = *material_id; + } } } } diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index 2351c7756d..57762bfc8a 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -13,7 +13,7 @@ use bevy_core_pipeline::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_render::{ camera::TemporalJitter, mesh::{Mesh, MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts}, @@ -37,7 +37,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( material_pipeline: Res>, mesh_pipeline: Res, render_materials: Res>>, - render_material_instances: Res>, + render_material_instances: Res, material_bind_group_allocator: Res>, asset_server: Res, mut mesh_vertex_buffer_layouts: ResMut, @@ -148,8 +148,13 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( view_key |= MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); - for material_id in render_material_instances.values().collect::>() { - let Some(material) = render_materials.get(*material_id) else { + for material_id in render_material_instances + .instances + .values() + .flat_map(|instance| instance.asset_id.try_typed::().ok()) + .collect::>() + { + let Some(material) = render_materials.get(material_id) else { continue; }; let Some(material_bind_group) = @@ -256,7 +261,7 @@ pub fn prepare_material_meshlet_meshes_prepass( pipeline_cache: Res, prepass_pipeline: Res>, render_materials: Res>>, - render_material_instances: Res>, + render_material_instances: Res, mut mesh_vertex_buffer_layouts: ResMut, material_bind_group_allocator: Res>, asset_server: Res, @@ -293,8 +298,13 @@ pub fn prepare_material_meshlet_meshes_prepass( view_key |= MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); - for material_id in render_material_instances.values().collect::>() { - let Some(material) = render_materials.get(*material_id) else { + for material_id in render_material_instances + .instances + .values() + .flat_map(|instance| instance.asset_id.try_typed::().ok()) + .collect::>() + { + let Some(material) = render_materials.get(material_id) else { continue; }; let Some(material_bind_group) = @@ -336,9 +346,12 @@ pub fn prepare_material_meshlet_meshes_prepass( shader_defs.push("MESHLET_MESH_MATERIAL_PASS".into()); let view_layout = if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { - prepass_pipeline.view_layout_motion_vectors.clone() + prepass_pipeline.internal.view_layout_motion_vectors.clone() } else { - prepass_pipeline.view_layout_no_motion_vectors.clone() + prepass_pipeline + .internal + .view_layout_no_motion_vectors + .clone() }; let fragment_shader = if view_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { @@ -357,7 +370,7 @@ pub fn prepare_material_meshlet_meshes_prepass( layout: vec![ view_layout, resource_manager.material_shade_bind_group_layout.clone(), - prepass_pipeline.material_layout.clone(), + prepass_pipeline.internal.material_layout.clone(), ], push_constant_ranges: vec![], vertex: VertexState { diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs index 2bc686fe43..0f4aab7509 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::Vec2; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::{ render_resource::BufferAddress, renderer::{RenderDevice, RenderQueue}, diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 2e483b210c..2375894613 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -79,7 +79,7 @@ use bevy_render::{ renderer::RenderDevice, settings::WgpuFeatures, view::{self, prepare_view_targets, Msaa, Visibility, VisibilityClass}, - ExtractSchedule, Render, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::components::Transform; use derive_more::From; @@ -277,12 +277,12 @@ impl Plugin for MeshletPlugin { .add_systems( Render, ( - perform_pending_meshlet_mesh_writes.in_set(RenderSet::PrepareAssets), + perform_pending_meshlet_mesh_writes.in_set(RenderSystems::PrepareAssets), configure_meshlet_views .after(prepare_view_targets) - .in_set(RenderSet::ManageViews), - prepare_meshlet_per_frame_resources.in_set(RenderSet::PrepareResources), - prepare_meshlet_view_bind_groups.in_set(RenderSet::PrepareBindGroups), + .in_set(RenderSystems::ManageViews), + prepare_meshlet_per_frame_resources.in_set(RenderSystems::PrepareResources), + prepare_meshlet_view_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ); } diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index 9fb1387aec..9b45d7676a 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -8,7 +8,7 @@ use bevy_core_pipeline::{ }; use bevy_ecs::{ component::Component, - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, query::AnyOf, resource::Resource, system::{Commands, Query, Res, ResMut}, diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 4989c2536b..cbd8445483 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -32,7 +32,7 @@ pub enum UvChannel { #[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] #[bind_group_data(StandardMaterialKey)] #[data(0, StandardMaterialUniform, binding_array(10))] -#[bindless] +#[bindless(index_table(range(0..31)))] #[reflect(Default, Debug, Clone)] pub struct StandardMaterial { /// The color of the surface of the material before lighting. @@ -437,8 +437,8 @@ pub struct StandardMaterial { /// the [`StandardMaterial::specular_tint_texture`] has no alpha value, it /// may be desirable to pack the values together and supply the same /// texture to both fields. - #[texture(27)] - #[sampler(28)] + #[cfg_attr(feature = "pbr_specular_textures", texture(27))] + #[cfg_attr(feature = "pbr_specular_textures", sampler(28))] #[cfg(feature = "pbr_specular_textures")] pub specular_texture: Option>, @@ -458,9 +458,9 @@ pub struct StandardMaterial { /// /// Like the fixed specular tint value, this texture map isn't supported in /// the deferred renderer. + #[cfg_attr(feature = "pbr_specular_textures", texture(29))] + #[cfg_attr(feature = "pbr_specular_textures", sampler(30))] #[cfg(feature = "pbr_specular_textures")] - #[texture(29)] - #[sampler(30)] pub specular_tint_texture: Option>, /// An extra thin translucent layer on top of the main PBR layer. This is @@ -1345,7 +1345,7 @@ impl From<&StandardMaterial> for StandardMaterialKey { impl Material for StandardMaterial { fn fragment_shader() -> ShaderRef { - PBR_SHADER_HANDLE.into() + shader_ref(bevy_asset::embedded_path!("render/pbr.wgsl")) } #[inline] @@ -1381,11 +1381,11 @@ impl Material for StandardMaterial { } fn prepass_fragment_shader() -> ShaderRef { - PBR_PREPASS_SHADER_HANDLE.into() + shader_ref(bevy_asset::embedded_path!("render/pbr_prepass.wgsl")) } fn deferred_fragment_shader() -> ShaderRef { - PBR_SHADER_HANDLE.into() + shader_ref(bevy_asset::embedded_path!("render/pbr.wgsl")) } #[cfg(feature = "meshlet")] diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index ea60f42667..35182910a9 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -3,27 +3,28 @@ mod prepass_bindings; use crate::{ alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, collect_meshes_for_gpu_building, material_bind_groups::MaterialBindGroupAllocator, - queue_material_meshes, setup_morph_and_skinning_defs, skin, DrawMesh, - EntitySpecializationTicks, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, - MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, RenderLightmaps, - RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, RenderPhaseType, - SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial, + queue_material_meshes, set_mesh_motion_vector_flags, setup_morph_and_skinning_defs, skin, + DrawMesh, EntitySpecializationTicks, Material, MaterialPipeline, MaterialPipelineKey, + MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, + RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, + RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView, StandardMaterial, }; use bevy_app::{App, Plugin, PreUpdate}; use bevy_render::{ alpha::AlphaMode, batching::gpu_preprocessing::GpuPreprocessingSupport, + load_shader_library, mesh::{allocator::MeshAllocator, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::prepare_assets, render_resource::binding_types::uniform_buffer, renderer::RenderAdapter, sync_world::RenderEntity, view::{RenderVisibilityRanges, RetainedViewEntity, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT}, - ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSet, + ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSystems, }; pub use prepass_bindings::*; -use bevy_asset::{load_internal_asset, weak_handle, AssetServer, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; @@ -57,24 +58,12 @@ use crate::meshlet::{ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::system::SystemChangeTick; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::view::RenderVisibleEntities; -use bevy_render::RenderSet::{PrepareAssets, PrepareResources}; +use bevy_render::RenderSystems::{PrepareAssets, PrepareResources}; use core::{hash::Hash, marker::PhantomData}; -pub const PREPASS_SHADER_HANDLE: Handle = - weak_handle!("ce810284-f1ae-4439-ab2e-0d6b204b6284"); - -pub const PREPASS_BINDINGS_SHADER_HANDLE: Handle = - weak_handle!("3e83537e-ae17-489c-a18a-999bc9c1d252"); - -pub const PREPASS_UTILS_SHADER_HANDLE: Handle = - weak_handle!("02e4643a-a14b-48eb-a339-0c47aeab0d7e"); - -pub const PREPASS_IO_SHADER_HANDLE: Handle = - weak_handle!("1c065187-c99b-4b7c-ba59-c1575482d2c9"); - /// Sets up everything required to use the prepass pipeline. /// /// This does not add the actual prepasses, see [`PrepassPlugin`] for that. @@ -91,33 +80,11 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - PREPASS_SHADER_HANDLE, - "prepass.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "prepass.wgsl"); - load_internal_asset!( - app, - PREPASS_BINDINGS_SHADER_HANDLE, - "prepass_bindings.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - PREPASS_UTILS_SHADER_HANDLE, - "prepass_utils.wgsl", - Shader::from_wgsl - ); - - load_internal_asset!( - app, - PREPASS_IO_SHADER_HANDLE, - "prepass_io.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "prepass_bindings.wgsl"); + load_shader_library!(app, "prepass_utils.wgsl"); + load_shader_library!(app, "prepass_io.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -126,7 +93,7 @@ where render_app .add_systems( Render, - prepare_prepass_view_bind_group::.in_set(RenderSet::PrepareBindGroups), + prepare_prepass_view_bind_group::.in_set(RenderSystems::PrepareBindGroups), ) .init_resource::() .init_resource::>>() @@ -216,12 +183,13 @@ where ( check_prepass_views_need_specialization.in_set(PrepareAssets), specialize_prepass_material_meshes:: - .in_set(RenderSet::PrepareMeshes) + .in_set(RenderSystems::PrepareMeshes) .after(prepare_assets::>) .after(prepare_assets::) - .after(collect_meshes_for_gpu_building), + .after(collect_meshes_for_gpu_building) + .after(set_mesh_motion_vector_flags), queue_prepass_material_meshes:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>) // queue_material_meshes only writes to `material_bind_group_id`, which `queue_prepass_material_meshes` doesn't read .ambiguous_with(queue_material_meshes::), @@ -232,7 +200,7 @@ where render_app.add_systems( Render, prepare_material_meshlet_meshes_prepass:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>) .before(queue_material_meshlet_meshes::) .run_if(resource_exists::), @@ -268,25 +236,34 @@ type PreviousMeshFilter = Or<(With, With)>; pub fn update_mesh_previous_global_transforms( mut commands: Commands, views: Query<&Camera, Or<(With, With)>>, - meshes: Query<(Entity, &GlobalTransform, Option<&PreviousGlobalTransform>), PreviousMeshFilter>, + new_meshes: Query< + (Entity, &GlobalTransform), + (PreviousMeshFilter, Without), + >, + mut meshes: Query<(&GlobalTransform, &mut PreviousGlobalTransform), PreviousMeshFilter>, ) { let should_run = views.iter().any(|camera| camera.is_active); if should_run { - for (entity, transform, old_previous_transform) in &meshes { + for (entity, transform) in &new_meshes { let new_previous_transform = PreviousGlobalTransform(transform.affine()); - // Make sure not to trigger change detection on - // `PreviousGlobalTransform` if the previous transform hasn't - // changed. - if old_previous_transform != Some(&new_previous_transform) { - commands.entity(entity).try_insert(new_previous_transform); - } + commands.entity(entity).try_insert(new_previous_transform); } + meshes.par_iter_mut().for_each(|(transform, mut previous)| { + previous.set_if_neq(PreviousGlobalTransform(transform.affine())); + }); } } #[derive(Resource)] pub struct PrepassPipeline { + pub internal: PrepassPipelineInternal, + pub material_pipeline: MaterialPipeline, +} + +/// Internal fields of the `PrepassPipeline` that don't need the generic bound +/// This is done as an optimization to not recompile the same code multiple time +pub struct PrepassPipelineInternal { pub view_layout_motion_vectors: BindGroupLayout, pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, @@ -295,7 +272,7 @@ pub struct PrepassPipeline { pub prepass_material_fragment_shader: Option>, pub deferred_material_vertex_shader: Option>, pub deferred_material_fragment_shader: Option>, - pub material_pipeline: MaterialPipeline, + pub default_prepass_shader: Handle, /// Whether skins will use uniform buffers on account of storage buffers /// being unavailable on this platform. @@ -306,8 +283,6 @@ pub struct PrepassPipeline { /// Whether binding arrays (a.k.a. bindless textures) are usable on the /// current render device. pub binding_arrays_are_usable: bool, - - _marker: PhantomData, } impl FromWorld for PrepassPipeline { @@ -372,8 +347,7 @@ impl FromWorld for PrepassPipeline { let depth_clip_control_supported = render_device .features() .contains(WgpuFeatures::DEPTH_CLIP_CONTROL); - - PrepassPipeline { + let internal = PrepassPipelineInternal { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), @@ -397,12 +371,15 @@ impl FromWorld for PrepassPipeline { ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, + default_prepass_shader: load_embedded_asset!(world, "prepass.wgsl"), material_layout: M::bind_group_layout(render_device), - material_pipeline: world.resource::>().clone(), skins_use_uniform_buffers: skin::skins_use_uniform_buffers(render_device), depth_clip_control_supported, binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter), - _marker: PhantomData, + }; + PrepassPipeline { + internal, + material_pipeline: world.resource::>().clone(), } } } @@ -418,15 +395,38 @@ where key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result { - let mut bind_group_layouts = vec![if key - .mesh_key + let mut shader_defs = Vec::new(); + if self.material_pipeline.bindless { + shader_defs.push("BINDLESS".into()); + } + let mut descriptor = self + .internal + .specialize(key.mesh_key, shader_defs, layout)?; + + // This is a bit risky because it's possible to change something that would + // break the prepass but be fine in the main pass. + // Since this api is pretty low-level it doesn't matter that much, but it is a potential issue. + M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?; + + Ok(descriptor) + } +} + +impl PrepassPipelineInternal { + fn specialize( + &self, + mesh_key: MeshPipelineKey, + shader_defs: Vec, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut shader_defs = shader_defs; + let mut bind_group_layouts = vec![if mesh_key .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { self.view_layout_motion_vectors.clone() } else { self.view_layout_no_motion_vectors.clone() }]; - let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); // Let the shader code know that it's running in a prepass pipeline. @@ -437,40 +437,29 @@ where // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.push(self.material_layout.clone()); - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); - shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); - - if key.mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) { + if mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) { shader_defs.push("DEPTH_PREPASS".into()); } - - if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) { + if mesh_key.contains(MeshPipelineKey::MAY_DISCARD) { shader_defs.push("MAY_DISCARD".into()); } - - let blend_key = key - .mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS); + let blend_key = mesh_key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS); if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); } if blend_key == MeshPipelineKey::BLEND_ALPHA { shader_defs.push("BLEND_ALPHA".into()); } - if layout.0.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } - // For directional light shadow map views, use unclipped depth via either the native GPU feature, // or emulated by setting depth in the fragment shader for GPUs that don't support it natively. - let emulate_unclipped_depth = key - .mesh_key - .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) + let emulate_unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) && !self.depth_clip_control_supported; if emulate_unclipped_depth { shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into()); @@ -482,36 +471,28 @@ where // https://github.com/bevyengine/bevy/pull/8877 shader_defs.push("PREPASS_FRAGMENT".into()); } - let unclipped_depth = key - .mesh_key - .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) + let unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO) && self.depth_clip_control_supported; - if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); shader_defs.push("VERTEX_UVS_A".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1)); } - if layout.0.contains(Mesh::ATTRIBUTE_UV_1) { shader_defs.push("VERTEX_UVS".into()); shader_defs.push("VERTEX_UVS_B".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2)); } - - if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { + if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { shader_defs.push("NORMAL_PREPASS".into()); } - - if key - .mesh_key - .intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) + if mesh_key.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into()); if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) { shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3)); - } else if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { + } else if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { warn!( "The default normal prepass expects the mesh to have vertex normal attributes." ); @@ -521,91 +502,62 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4)); } } - - if key - .mesh_key + if mesh_key .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into()); } - - if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("DEFERRED_PREPASS".into()); } - - if key.mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) { + if mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) { shader_defs.push("LIGHTMAP".into()); } - if key - .mesh_key - .contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) - { + if mesh_key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) { shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into()); } - if layout.0.contains(Mesh::ATTRIBUTE_COLOR) { shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7)); } - - if key - .mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) - { + if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } - - if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { + if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { shader_defs.push("HAS_PREVIOUS_SKIN".into()); } - - if key.mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { + if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { shader_defs.push("HAS_PREVIOUS_MORPH".into()); } - - // If bindless mode is on, add a `BINDLESS` define. - if self.material_pipeline.bindless { - shader_defs.push("BINDLESS".into()); - } - if self.binding_arrays_are_usable { shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); } - - if key - .mesh_key - .contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) - { + if mesh_key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) { shader_defs.push("VISIBILITY_RANGE_DITHER".into()); } - - if key.mesh_key.intersects( + if mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS, ) { shader_defs.push("PREPASS_FRAGMENT".into()); } - let bind_group = setup_morph_and_skinning_defs( &self.mesh_layouts, layout, 5, - &key.mesh_key, + &mesh_key, &mut shader_defs, &mut vertex_attributes, self.skins_use_uniform_buffers, ); bind_group_layouts.insert(1, bind_group); - let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; - // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 let mut targets = prepass_target_descriptors( - key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS), - key.mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS), - key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS), + mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS), + mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS), + mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS), ); if targets.iter().all(Option::is_none) { @@ -619,20 +571,20 @@ where // prepass shader, or we are emulating unclipped depth in the fragment shader. let fragment_required = !targets.is_empty() || emulate_unclipped_depth - || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) + || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && self.prepass_material_fragment_shader.is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material - let frag_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { match self.deferred_material_fragment_shader.clone() { Some(frag_shader_handle) => frag_shader_handle, - _ => PREPASS_SHADER_HANDLE, + None => self.default_prepass_shader.clone(), } } else { match self.prepass_material_fragment_shader.clone() { Some(frag_shader_handle) => frag_shader_handle, - _ => PREPASS_SHADER_HANDLE, + None => self.default_prepass_shader.clone(), } }; @@ -645,19 +597,18 @@ where }); // Use the vertex shader from the material if present - let vert_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { if let Some(handle) = &self.deferred_material_vertex_shader { handle.clone() } else { - PREPASS_SHADER_HANDLE + self.default_prepass_shader.clone() } } else if let Some(handle) = &self.prepass_material_vertex_shader { handle.clone() } else { - PREPASS_SHADER_HANDLE + self.default_prepass_shader.clone() }; - - let mut descriptor = RenderPipelineDescriptor { + let descriptor = RenderPipelineDescriptor { vertex: VertexState { shader: vert_shader_handle, entry_point: "vertex".into(), @@ -667,7 +618,7 @@ where fragment, layout: bind_group_layouts, primitive: PrimitiveState { - topology: key.mesh_key.primitive_topology(), + topology: mesh_key.primitive_topology(), strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: None, @@ -692,7 +643,7 @@ where }, }), multisample: MultisampleState { - count: key.mesh_key.msaa_samples(), + count: mesh_key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, @@ -700,12 +651,6 @@ where label: Some("prepass_pipeline".into()), zero_initialize_workgroup_memory: false, }; - - // This is a bit risky because it's possible to change something that would - // break the prepass but be fine in the main pass. - // Since this api is pretty low-level it doesn't matter that much, but it is a potential issue. - M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?; - Ok(descriptor) } } @@ -790,7 +735,7 @@ pub fn prepare_prepass_view_bind_group( ) { prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group( "prepass_view_no_motion_vectors_bind_group", - &prepass_pipeline.view_layout_no_motion_vectors, + &prepass_pipeline.internal.view_layout_no_motion_vectors, &BindGroupEntries::with_indices(( (0, view_binding.clone()), (1, globals_binding.clone()), @@ -801,7 +746,7 @@ pub fn prepare_prepass_view_bind_group( if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() { prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group( "prepass_view_motion_vectors_bind_group", - &prepass_pipeline.view_layout_motion_vectors, + &prepass_pipeline.internal.view_layout_motion_vectors, &BindGroupEntries::with_indices(( (0, view_binding), (1, globals_binding), @@ -896,7 +841,7 @@ pub fn specialize_prepass_material_meshes( render_meshes: Res>, render_materials: Res>>, render_mesh_instances: Res, - render_material_instances: Res>, + render_material_instances: Res, render_lightmaps: Res, render_visibility_ranges: Res, material_bind_group_allocator: Res>, @@ -962,6 +907,17 @@ pub fn specialize_prepass_material_meshes( .or_default(); for (_, visible_entity) in visible_entities.iter::() { + let Some(material_instance) = render_material_instances.instances.get(visible_entity) + else { + continue; + }; + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(visible_entity) @@ -973,15 +929,7 @@ pub fn specialize_prepass_material_meshes( if !needs_specialization { continue; } - - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { - continue; - }; - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) - else { - continue; - }; - let Some(material) = render_materials.get(*material_asset_id) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; let Some(material_bind_group) = @@ -1004,12 +952,18 @@ pub fn specialize_prepass_material_meshes( AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add - | AlphaMode::Multiply => continue, + | AlphaMode::Multiply => { + // In case this material was previously in a valid alpha_mode, remove it to + // stop the queue system from assuming its retained cache to be valid. + view_specialized_material_pipeline_cache.remove(visible_entity); + continue; + } } if material.properties.reads_view_transmission_texture { // No-op: Materials reading from `ViewTransmissionTexture` are not rendered in the `Opaque3d` // phase, and are therefore also excluded from the prepass much like alpha-blended materials. + view_specialized_material_pipeline_cache.remove(visible_entity); continue; } @@ -1086,7 +1040,7 @@ pub fn specialize_prepass_material_meshes( pub fn queue_prepass_material_meshes( render_mesh_instances: Res, render_materials: Res>>, - render_material_instances: Res>, + render_material_instances: Res, mesh_allocator: Res, gpu_preprocessing_support: Res, mut opaque_prepass_render_phases: ResMut>, @@ -1146,14 +1100,18 @@ pub fn queue_prepass_material_meshes( continue; } - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + let Some(material_instance) = render_material_instances.instances.get(visible_entity) + else { + continue; + }; + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { continue; }; let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; }; - let Some(material) = render_materials.get(*material_asset_id) else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 9394380f71..fa09120725 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -1,14 +1,14 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_color::{ColorToComponents, LinearRgba}; use bevy_ecs::prelude::*; use bevy_math::{Vec3, Vec4}; use bevy_render::{ extract_component::ExtractComponentPlugin, - render_resource::{DynamicUniformBuffer, Shader, ShaderType}, + load_shader_library, + render_resource::{DynamicUniformBuffer, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ExtractedView, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use crate::{DistanceFog, FogFalloff}; @@ -126,15 +126,12 @@ pub struct ViewFogUniformOffset { pub offset: u32, } -/// Handle for the fog WGSL Shader internal asset -pub const FOG_SHADER_HANDLE: Handle = weak_handle!("e943f446-2856-471c-af5e-68dd276eec42"); - /// A plugin that consolidates fog extraction, preparation and related resources/assets pub struct FogPlugin; impl Plugin for FogPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, FOG_SHADER_HANDLE, "fog.wgsl", Shader::from_wgsl); + load_shader_library!(app, "fog.wgsl"); app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); @@ -142,7 +139,7 @@ impl Plugin for FogPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_systems(Render, prepare_fog.in_set(RenderSet::PrepareResources)); + .add_systems(Render, prepare_fog.in_set(RenderSystems::PrepareResources)); } } } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 03431037c1..eaa7e857b7 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -9,7 +9,7 @@ use core::num::{NonZero, NonZeroU64}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, experimental::mip_generation::ViewDepthPyramid, @@ -27,7 +27,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_render::batching::gpu_preprocessing::{ - IndirectParametersGpuMetadata, UntypedPhaseIndirectParametersBuffers, + GpuPreprocessingMode, IndirectParametersGpuMetadata, UntypedPhaseIndirectParametersBuffers, }; use bevy_render::{ batching::gpu_preprocessing::{ @@ -50,7 +50,7 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice, RenderQueue}, settings::WgpuFeatures, view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_utils::TypeIdMap; use bitflags::bitflags; @@ -63,19 +63,6 @@ use crate::{ use super::{ShadowView, ViewLightEntities}; -/// The handle to the `mesh_preprocess.wgsl` compute shader. -pub const MESH_PREPROCESS_SHADER_HANDLE: Handle = - weak_handle!("c8579292-cf92-43b5-9c5a-ec5bd4e44d12"); -/// The handle to the `mesh_preprocess_types.wgsl` compute shader. -pub const MESH_PREPROCESS_TYPES_SHADER_HANDLE: Handle = - weak_handle!("06f797ef-a106-4098-9a2e-20a73aa182e2"); -/// The handle to the `reset_indirect_batch_sets.wgsl` compute shader. -pub const RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE: Handle = - weak_handle!("045fb176-58e2-4e76-b241-7688d761bb23"); -/// The handle to the `build_indirect_params.wgsl` compute shader. -pub const BUILD_INDIRECT_PARAMS_SHADER_HANDLE: Handle = - weak_handle!("133b01f0-3eaf-4590-9ee9-f0cf91a00b71"); - /// The GPU workgroup size. const WORKGROUP_SIZE: usize = 64; @@ -258,6 +245,8 @@ pub struct PreprocessPhasePipelines { pub struct PreprocessPipeline { /// The bind group layout for the compute shader. pub bind_group_layout: BindGroupLayout, + /// The shader asset handle. + pub shader: Handle, /// The pipeline ID for the compute shader. /// /// This gets filled in `prepare_preprocess_pipelines`. @@ -272,6 +261,8 @@ pub struct PreprocessPipeline { pub struct ResetIndirectBatchSetsPipeline { /// The bind group layout for the compute shader. pub bind_group_layout: BindGroupLayout, + /// The shader asset handle. + pub shader: Handle, /// The pipeline ID for the compute shader. /// /// This gets filled in `prepare_preprocess_pipelines`. @@ -283,6 +274,8 @@ pub struct ResetIndirectBatchSetsPipeline { pub struct BuildIndirectParametersPipeline { /// The bind group layout for the compute shader. pub bind_group_layout: BindGroupLayout, + /// The shader asset handle. + pub shader: Handle, /// The pipeline ID for the compute shader. /// /// This gets filled in `prepare_preprocess_pipelines`. @@ -434,36 +427,9 @@ pub struct SkipGpuPreprocess; impl Plugin for GpuMeshPreprocessPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - MESH_PREPROCESS_SHADER_HANDLE, - "mesh_preprocess.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE, - "reset_indirect_batch_sets.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - BUILD_INDIRECT_PARAMS_SHADER_HANDLE, - "build_indirect_params.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - BUILD_INDIRECT_PARAMS_SHADER_HANDLE, - "build_indirect_params.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - BUILD_INDIRECT_PARAMS_SHADER_HANDLE, - "build_indirect_params.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "mesh_preprocess.wgsl"); + embedded_asset!(app, "reset_indirect_batch_sets.wgsl"); + embedded_asset!(app, "build_indirect_params.wgsl"); } fn finish(&self, app: &mut App) { @@ -486,14 +452,14 @@ impl Plugin for GpuMeshPreprocessPlugin { .add_systems( Render, ( - prepare_preprocess_pipelines.in_set(RenderSet::Prepare), + prepare_preprocess_pipelines.in_set(RenderSystems::Prepare), prepare_preprocess_bind_groups .run_if(resource_exists::>) - .in_set(RenderSet::PrepareBindGroups), - write_mesh_culling_data_buffer.in_set(RenderSet::PrepareResourcesFlush), + .in_set(RenderSystems::PrepareBindGroups), + write_mesh_culling_data_buffer.in_set(RenderSystems::PrepareResourcesFlush), ), ) .add_render_graph_node::( @@ -1188,26 +1154,41 @@ fn run_build_indirect_parameters_node( impl PreprocessPipelines { /// Returns true if the preprocessing and indirect parameters pipelines have /// been loaded or false otherwise. - pub(crate) fn pipelines_are_loaded(&self, pipeline_cache: &PipelineCache) -> bool { - self.direct_preprocess.is_loaded(pipeline_cache) - && self - .gpu_frustum_culling_preprocess - .is_loaded(pipeline_cache) - && self - .early_gpu_occlusion_culling_preprocess - .is_loaded(pipeline_cache) - && self - .late_gpu_occlusion_culling_preprocess - .is_loaded(pipeline_cache) - && self - .gpu_frustum_culling_build_indexed_indirect_params - .is_loaded(pipeline_cache) - && self - .gpu_frustum_culling_build_non_indexed_indirect_params - .is_loaded(pipeline_cache) - && self.early_phase.is_loaded(pipeline_cache) - && self.late_phase.is_loaded(pipeline_cache) - && self.main_phase.is_loaded(pipeline_cache) + pub(crate) fn pipelines_are_loaded( + &self, + pipeline_cache: &PipelineCache, + preprocessing_support: &GpuPreprocessingSupport, + ) -> bool { + match preprocessing_support.max_supported_mode { + GpuPreprocessingMode::None => false, + GpuPreprocessingMode::PreprocessingOnly => { + self.direct_preprocess.is_loaded(pipeline_cache) + && self + .gpu_frustum_culling_preprocess + .is_loaded(pipeline_cache) + } + GpuPreprocessingMode::Culling => { + self.direct_preprocess.is_loaded(pipeline_cache) + && self + .gpu_frustum_culling_preprocess + .is_loaded(pipeline_cache) + && self + .early_gpu_occlusion_culling_preprocess + .is_loaded(pipeline_cache) + && self + .late_gpu_occlusion_culling_preprocess + .is_loaded(pipeline_cache) + && self + .gpu_frustum_culling_build_indexed_indirect_params + .is_loaded(pipeline_cache) + && self + .gpu_frustum_culling_build_non_indexed_indirect_params + .is_loaded(pipeline_cache) + && self.early_phase.is_loaded(pipeline_cache) + && self.late_phase.is_loaded(pipeline_cache) + && self.main_phase.is_loaded(pipeline_cache) + } + } } } @@ -1292,7 +1273,7 @@ impl SpecializedComputePipeline for PreprocessPipeline { } else { vec![] }, - shader: MESH_PREPROCESS_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -1363,18 +1344,27 @@ impl FromWorld for PreprocessPipelines { &build_non_indexed_indirect_params_bind_group_layout_entries, ); + let preprocess_shader = load_embedded_asset!(world, "mesh_preprocess.wgsl"); + let reset_indirect_batch_sets_shader = + load_embedded_asset!(world, "reset_indirect_batch_sets.wgsl"); + let build_indirect_params_shader = + load_embedded_asset!(world, "build_indirect_params.wgsl"); + let preprocess_phase_pipelines = PreprocessPhasePipelines { reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline { bind_group_layout: reset_indirect_batch_sets_bind_group_layout.clone(), + shader: reset_indirect_batch_sets_shader, pipeline_id: None, }, gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline { bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(), + shader: build_indirect_params_shader.clone(), pipeline_id: None, }, gpu_occlusion_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline { bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(), + shader: build_indirect_params_shader.clone(), pipeline_id: None, }, }; @@ -1382,27 +1372,33 @@ impl FromWorld for PreprocessPipelines { PreprocessPipelines { direct_preprocess: PreprocessPipeline { bind_group_layout: direct_bind_group_layout, + shader: preprocess_shader.clone(), pipeline_id: None, }, gpu_frustum_culling_preprocess: PreprocessPipeline { bind_group_layout: gpu_frustum_culling_bind_group_layout, + shader: preprocess_shader.clone(), pipeline_id: None, }, early_gpu_occlusion_culling_preprocess: PreprocessPipeline { bind_group_layout: gpu_early_occlusion_culling_bind_group_layout, + shader: preprocess_shader.clone(), pipeline_id: None, }, late_gpu_occlusion_culling_preprocess: PreprocessPipeline { bind_group_layout: gpu_late_occlusion_culling_bind_group_layout, + shader: preprocess_shader, pipeline_id: None, }, gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline { bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(), + shader: build_indirect_params_shader.clone(), pipeline_id: None, }, gpu_frustum_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline { bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(), + shader: build_indirect_params_shader, pipeline_id: None, }, early_phase: preprocess_phase_pipelines.clone(), @@ -1510,6 +1506,7 @@ pub fn prepare_preprocess_pipelines( SpecializedComputePipelines, >, preprocess_pipelines: ResMut, + gpu_preprocessing_support: Res, ) { let preprocess_pipelines = preprocess_pipelines.into_inner(); @@ -1523,22 +1520,25 @@ pub fn prepare_preprocess_pipelines( &mut specialized_preprocess_pipelines, PreprocessPipelineKey::FRUSTUM_CULLING, ); - preprocess_pipelines - .early_gpu_occlusion_culling_preprocess - .prepare( - &pipeline_cache, - &mut specialized_preprocess_pipelines, - PreprocessPipelineKey::FRUSTUM_CULLING - | PreprocessPipelineKey::OCCLUSION_CULLING - | PreprocessPipelineKey::EARLY_PHASE, - ); - preprocess_pipelines - .late_gpu_occlusion_culling_preprocess - .prepare( - &pipeline_cache, - &mut specialized_preprocess_pipelines, - PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING, - ); + + if gpu_preprocessing_support.is_culling_supported() { + preprocess_pipelines + .early_gpu_occlusion_culling_preprocess + .prepare( + &pipeline_cache, + &mut specialized_preprocess_pipelines, + PreprocessPipelineKey::FRUSTUM_CULLING + | PreprocessPipelineKey::OCCLUSION_CULLING + | PreprocessPipelineKey::EARLY_PHASE, + ); + preprocess_pipelines + .late_gpu_occlusion_culling_preprocess + .prepare( + &pipeline_cache, + &mut specialized_preprocess_pipelines, + PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING, + ); + } let mut build_indirect_parameters_pipeline_key = BuildIndirectParametersPipelineKey::empty(); @@ -1568,6 +1568,10 @@ pub fn prepare_preprocess_pipelines( build_indirect_parameters_pipeline_key, ); + if !gpu_preprocessing_support.is_culling_supported() { + return; + } + for (preprocess_phase_pipelines, build_indirect_parameters_phase_pipeline_key) in [ ( &mut preprocess_pipelines.early_phase, @@ -1634,7 +1638,7 @@ impl SpecializedComputePipeline for ResetIndirectBatchSetsPipeline { label: Some("reset indirect batch sets".into()), layout: vec![self.bind_group_layout.clone()], push_constant_ranges: vec![], - shader: RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs: vec![], entry_point: "main".into(), zero_initialize_workgroup_memory: false, @@ -1688,7 +1692,7 @@ impl SpecializedComputePipeline for BuildIndirectParametersPipeline { label: Some(label.into()), layout: vec![self.bind_group_layout.clone()], push_constant_ranges: vec![], - shader: BUILD_INDIRECT_PARAMS_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "main".into(), zero_initialize_workgroup_memory: false, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6f3216d896..f57ba9adf3 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -8,13 +8,13 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, + entity::{EntityHashMap, EntityHashSet}, prelude::*, system::lifetimeless::Read, }; use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; -use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_platform_support::hash::FixedHasher; +use bevy_platform::collections::{HashMap, HashSet}; +use bevy_platform::hash::FixedHasher; use bevy_render::experimental::occlusion_culling::{ OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, }; @@ -220,7 +220,18 @@ pub fn extract_lights( mut commands: Commands, point_light_shadow_map: Extract>, directional_light_shadow_map: Extract>, - global_point_lights: Extract>, + global_visible_clusterable: Extract>, + previous_point_lights: Query< + Entity, + ( + With, + With, + ), + >, + previous_spot_lights: Query< + Entity, + (With, With), + >, point_lights: Extract< Query<( Entity, @@ -276,6 +287,22 @@ pub fn extract_lights( if directional_light_shadow_map.is_changed() { commands.insert_resource(directional_light_shadow_map.clone()); } + + // Clear previous visible entities for all point/spot lights as they might not be in the + // `global_visible_clusterable` list anymore. + commands.try_insert_batch( + previous_point_lights + .iter() + .map(|render_entity| (render_entity, RenderCubemapVisibleEntities::default())) + .collect::>(), + ); + commands.try_insert_batch( + previous_spot_lights + .iter() + .map(|render_entity| (render_entity, RenderVisibleMeshEntities::default())) + .collect::>(), + ); + // This is the point light shadow map texel size for one face of the cube as a distance of 1.0 // world unit from the light. // point_light_texel_size = 2.0 * 1.0 * tan(PI / 4.0) / cube face width in texels @@ -286,7 +313,7 @@ pub fn extract_lights( let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32; let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len); - for entity in global_point_lights.iter().copied() { + for entity in global_visible_clusterable.iter().copied() { let Ok(( main_entity, render_entity, @@ -350,7 +377,7 @@ pub fn extract_lights( commands.try_insert_batch(point_lights_values); let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len); - for entity in global_point_lights.iter().copied() { + for entity in global_visible_clusterable.iter().copied() { if let Ok(( main_entity, render_entity, @@ -1729,7 +1756,7 @@ pub fn specialize_shadows( Res>, Res, Res>>, - Res>, + Res, Res>, ), shadow_render_phases: Res>, @@ -1809,6 +1836,19 @@ pub fn specialize_shadows( .or_default(); for (_, visible_entity) in visible_entities.iter().copied() { + let Some(material_instances) = + render_material_instances.instances.get(&visible_entity) + else { + continue; + }; + let Ok(material_asset_id) = material_instances.asset_id.try_typed::() else { + continue; + }; + let Some(mesh_instance) = + render_mesh_instances.render_mesh_queue_data(visible_entity) + else { + continue; + }; let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(&visible_entity) @@ -1820,10 +1860,7 @@ pub fn specialize_shadows( if !needs_specialization { continue; } - - let Some(mesh_instance) = - render_mesh_instances.render_mesh_queue_data(visible_entity) - else { + let Some(material) = render_materials.get(material_asset_id) else { continue; }; if !mesh_instance @@ -1832,12 +1869,6 @@ pub fn specialize_shadows( { continue; } - let Some(material_asset_id) = render_material_instances.get(&visible_entity) else { - continue; - }; - let Some(material) = render_materials.get(*material_asset_id) else { - continue; - }; let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group) else { @@ -1907,7 +1938,7 @@ pub fn queue_shadows( shadow_draw_functions: Res>, render_mesh_instances: Res, render_materials: Res>>, - render_material_instances: Res>, + render_material_instances: Res, mut shadow_render_phases: ResMut>, gpu_preprocessing_support: Res, mesh_allocator: Res, @@ -1990,10 +2021,14 @@ pub fn queue_shadows( continue; } - let Some(material_asset_id) = render_material_instances.get(&main_entity) else { + let Some(material_instance) = render_material_instances.instances.get(&main_entity) + else { continue; }; - let Some(material) = render_materials.get(*material_asset_id) else { + let Ok(material_asset_id) = material_instance.asset_id.try_typed::() else { + continue; + }; + let Some(material) = render_materials.get(material_asset_id) else { continue; }; @@ -2239,18 +2274,12 @@ impl ShadowPassNode { world: &'w World, is_late: bool, ) -> Result<(), NodeRunError> { - let diagnostics = render_context.diagnostic_recorder(); - - let view_entity = graph.view_entity(); - let Some(shadow_render_phases) = world.get_resource::>() else { return Ok(()); }; - let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows"); - - if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) { + if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) { for view_light_entity in view_lights.lights.iter().copied() { let Ok((view_light, extracted_light_view, occlusion_culling)) = self.view_light_query.get_manual(world, view_light_entity) @@ -2307,8 +2336,6 @@ impl ShadowPassNode { } } - time_span.end(render_context.command_encoder()); - Ok(()) } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9f0c805c33..8c408233e1 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,6 +1,6 @@ use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; use allocator::MeshAllocator; -use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetId}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, @@ -16,7 +16,7 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; -use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_render::{ batching::{ gpu_preprocessing::{ @@ -49,7 +49,6 @@ use bevy_utils::{default, Parallel, TypeIdMap}; use core::any::TypeId; use core::mem::size_of; use material_bind_groups::MaterialBindingId; -use render::skin; use tracing::{error, warn}; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; @@ -75,7 +74,7 @@ use bevy_render::camera::TemporalJitter; use bevy_render::prelude::Msaa; use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::view::ExtractedView; -use bevy_render::RenderSet::PrepareAssets; +use bevy_render::RenderSystems::PrepareAssets; use bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; @@ -102,22 +101,6 @@ impl MeshRenderPlugin { } } -pub const FORWARD_IO_HANDLE: Handle = weak_handle!("38111de1-6e35-4dbb-877b-7b6f9334baf6"); -pub const MESH_VIEW_TYPES_HANDLE: Handle = - weak_handle!("979493db-4ae1-4003-b5c6-fcbb88b152a2"); -pub const MESH_VIEW_BINDINGS_HANDLE: Handle = - weak_handle!("c6fe674b-4c21-4d4b-867a-352848da5337"); -pub const MESH_TYPES_HANDLE: Handle = weak_handle!("a4a3fc2e-a57e-4083-a8ab-2840176927f2"); -pub const MESH_BINDINGS_HANDLE: Handle = - weak_handle!("84e7f9e6-e566-4a61-914e-c568f5dabf49"); -pub const MESH_FUNCTIONS_HANDLE: Handle = - weak_handle!("c46aa0f0-6c0c-4b3a-80bf-d8213c771f12"); -pub const MESH_SHADER_HANDLE: Handle = weak_handle!("1a7bbae8-4b4f-48a7-b53b-e6822e56f321"); -pub const SKINNING_HANDLE: Handle = weak_handle!("7474e812-2506-4cbf-9de3-fe07e5c6ff24"); -pub const MORPH_HANDLE: Handle = weak_handle!("da30aac7-34cc-431d-a07f-15b1a783008c"); -pub const OCCLUSION_CULLING_HANDLE: Handle = - weak_handle!("eaea07d9-7516-482c-aa42-6f8e9927e1f0"); - /// How many textures are allowed in the view bind group layout (`@group(0)`) before /// broader compatibility with WebGL and WebGPU is at risk, due to the minimum guaranteed /// values for `MAX_TEXTURE_IMAGE_UNITS` (in WebGL) and `maxSampledTexturesPerShaderStage` (in WebGPU), @@ -131,45 +114,28 @@ pub const MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES: usize = 10; impl Plugin for MeshRenderPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, FORWARD_IO_HANDLE, "forward_io.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - MESH_VIEW_TYPES_HANDLE, - "mesh_view_types.wgsl", - Shader::from_wgsl_with_defs, - vec![ - ShaderDefVal::UInt( - "MAX_DIRECTIONAL_LIGHTS".into(), - MAX_DIRECTIONAL_LIGHTS as u32 - ), - ShaderDefVal::UInt( - "MAX_CASCADES_PER_LIGHT".into(), - MAX_CASCADES_PER_LIGHT as u32, - ) - ] - ); - load_internal_asset!( - app, - MESH_VIEW_BINDINGS_HANDLE, - "mesh_view_bindings.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, MESH_TYPES_HANDLE, "mesh_types.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - MESH_FUNCTIONS_HANDLE, - "mesh_functions.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, MESH_SHADER_HANDLE, "mesh.wgsl", Shader::from_wgsl); - load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); - load_internal_asset!(app, MORPH_HANDLE, "morph.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - OCCLUSION_CULLING_HANDLE, - "occlusion_culling.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "forward_io.wgsl"); + load_shader_library!(app, "mesh_view_types.wgsl", |settings| *settings = + ShaderSettings { + shader_defs: vec![ + ShaderDefVal::UInt( + "MAX_DIRECTIONAL_LIGHTS".into(), + MAX_DIRECTIONAL_LIGHTS as u32 + ), + ShaderDefVal::UInt( + "MAX_CASCADES_PER_LIGHT".into(), + MAX_CASCADES_PER_LIGHT as u32, + ) + ] + }); + load_shader_library!(app, "mesh_view_bindings.wgsl"); + load_shader_library!(app, "mesh_types.wgsl"); + load_shader_library!(app, "mesh_functions.wgsl"); + load_shader_library!(app, "skinning.wgsl"); + load_shader_library!(app, "morph.wgsl"); + load_shader_library!(app, "occlusion_culling.wgsl"); + + embedded_asset!(app, "mesh.wgsl"); if app.get_sub_app(RenderApp).is_none() { return; @@ -194,10 +160,12 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::() + .init_resource::() .configure_sets( ExtractSchedule, - ExtractMeshesSet.after(view::extract_visibility_ranges), + MeshExtractionSystems + .after(view::extract_visibility_ranges) + .after(late_sweep_material_instances), ) .add_systems( ExtractSchedule, @@ -205,22 +173,22 @@ impl Plugin for MeshRenderPlugin { extract_skins, extract_morphs, gpu_preprocessing::clear_batched_gpu_instance_buffers:: - .before(ExtractMeshesSet), + .before(MeshExtractionSystems), ), ) .add_systems( Render, ( - set_mesh_motion_vector_flags.in_set(RenderSet::PrepareMeshes), - prepare_skins.in_set(RenderSet::PrepareResources), - prepare_morphs.in_set(RenderSet::PrepareResources), - prepare_mesh_bind_groups.in_set(RenderSet::PrepareBindGroups), + set_mesh_motion_vector_flags.in_set(RenderSystems::PrepareMeshes), + prepare_skins.in_set(RenderSystems::PrepareResources), + prepare_morphs.in_set(RenderSystems::PrepareResources), + prepare_mesh_bind_groups.in_set(RenderSystems::PrepareBindGroups), prepare_mesh_view_bind_groups - .in_set(RenderSet::PrepareBindGroups) + .in_set(RenderSystems::PrepareBindGroups) .after(prepare_oit_buffers), no_gpu_preprocessing::clear_batched_cpu_instance_buffers:: - .in_set(RenderSet::Cleanup) - .after(RenderSet::Render), + .in_set(RenderSystems::Cleanup) + .after(RenderSystems::Render), ), ); } @@ -258,17 +226,17 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .add_systems( ExtractSchedule, - extract_meshes_for_gpu_building.in_set(ExtractMeshesSet), + extract_meshes_for_gpu_building.in_set(MeshExtractionSystems), ) .add_systems( Render, ( gpu_preprocessing::write_batched_instance_buffers:: - .in_set(RenderSet::PrepareResourcesFlush), + .in_set(RenderSystems::PrepareResourcesFlush), gpu_preprocessing::delete_old_work_item_buffers:: - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), collect_meshes_for_gpu_building - .in_set(RenderSet::PrepareMeshes) + .in_set(RenderSystems::PrepareMeshes) // This must be before // `set_mesh_motion_vector_flags` so it doesn't // overwrite those flags. @@ -283,12 +251,12 @@ impl Plugin for MeshRenderPlugin { .insert_resource(cpu_batched_instance_buffer) .add_systems( ExtractSchedule, - extract_meshes_for_cpu_building.in_set(ExtractMeshesSet), + extract_meshes_for_cpu_building.in_set(MeshExtractionSystems), ) .add_systems( Render, no_gpu_preprocessing::write_batched_instance_buffer:: - .in_set(RenderSet::PrepareResourcesFlush), + .in_set(RenderSystems::PrepareResourcesFlush), ); }; @@ -309,13 +277,10 @@ impl Plugin for MeshRenderPlugin { // Load the mesh_bindings shader module here as it depends on runtime information about // whether storage buffers are supported, or the maximum uniform buffer binding size. - load_internal_asset!( - app, - MESH_BINDINGS_HANDLE, - "mesh_bindings.wgsl", - Shader::from_wgsl_with_defs, - mesh_bindings_shader_defs - ); + load_shader_library!(app, "mesh_bindings.wgsl", move |settings| *settings = + ShaderSettings { + shader_defs: mesh_bindings_shader_defs.clone(), + }); } } @@ -836,12 +801,33 @@ pub struct RenderMeshInstanceGpuQueues(Parallel); pub struct MeshesToReextractNextFrame(MainEntityHashSet); impl RenderMeshInstanceShared { - fn from_components( + /// A gpu builder will provide the mesh instance id + /// during [`RenderMeshInstanceGpuBuilder::update`]. + fn for_gpu_building( previous_transform: Option<&PreviousGlobalTransform>, mesh: &Mesh3d, tag: Option<&MeshTag>, not_shadow_caster: bool, no_automatic_batching: bool, + ) -> Self { + Self::for_cpu_building( + previous_transform, + mesh, + tag, + default(), + not_shadow_caster, + no_automatic_batching, + ) + } + + /// The cpu builder does not have an equivalent [`RenderMeshInstanceGpuBuilder::update`]. + fn for_cpu_building( + previous_transform: Option<&PreviousGlobalTransform>, + mesh: &Mesh3d, + tag: Option<&MeshTag>, + material_bindings_index: MaterialBindingId, + not_shadow_caster: bool, + no_automatic_batching: bool, ) -> Self { let mut mesh_instance_flags = RenderMeshInstanceFlags::empty(); mesh_instance_flags.set(RenderMeshInstanceFlags::SHADOW_CASTER, !not_shadow_caster); @@ -857,8 +843,7 @@ impl RenderMeshInstanceShared { RenderMeshInstanceShared { mesh_asset_id: mesh.id(), flags: mesh_instance_flags, - // This gets filled in later, during `RenderMeshGpuBuilder::update`. - material_bindings_index: default(), + material_bindings_index, lightmap_slab_index: None, tag: tag.map_or(0, |i| **i), } @@ -896,37 +881,6 @@ pub struct RenderMeshInstancesCpu(MainEntityHashMap); #[derive(Default, Deref, DerefMut)] pub struct RenderMeshInstancesGpu(MainEntityHashMap); -/// Maps each mesh instance to the material ID, and allocated binding ID, -/// associated with that mesh instance. -#[derive(Resource, Default)] -pub struct RenderMeshMaterialIds { - /// Maps the mesh instance to the material ID. - mesh_to_material: MainEntityHashMap, -} - -impl RenderMeshMaterialIds { - /// Returns the mesh material ID for the entity with the given mesh, or a - /// dummy mesh material ID if the mesh has no material ID. - /// - /// Meshes almost always have materials, but in very specific circumstances - /// involving custom pipelines they won't. (See the - /// `specialized_mesh_pipelines` example.) - pub(crate) fn mesh_material(&self, entity: MainEntity) -> UntypedAssetId { - self.mesh_to_material - .get(&entity) - .cloned() - .unwrap_or(AssetId::::invalid().into()) - } - - pub(crate) fn insert(&mut self, mesh_entity: MainEntity, material_id: UntypedAssetId) { - self.mesh_to_material.insert(mesh_entity, material_id); - } - - pub(crate) fn remove(&mut self, main_entity: MainEntity) { - self.mesh_to_material.remove(&main_entity); - } -} - impl RenderMeshInstances { /// Creates a new [`RenderMeshInstances`] instance. fn new(use_gpu_instance_buffer_builder: bool) -> RenderMeshInstances { @@ -938,7 +892,7 @@ impl RenderMeshInstances { } /// Returns the ID of the mesh asset attached to the given entity, if any. - pub(crate) fn mesh_asset_id(&self, entity: MainEntity) -> Option> { + pub fn mesh_asset_id(&self, entity: MainEntity) -> Option> { match *self { RenderMeshInstances::CpuBuilding(ref instances) => instances.mesh_asset_id(entity), RenderMeshInstances::GpuBuilding(ref instances) => instances.mesh_asset_id(entity), @@ -1129,7 +1083,7 @@ impl RenderMeshInstanceGpuBuilder { current_input_buffer: &mut InstanceInputUniformBuffer, previous_input_buffer: &mut InstanceInputUniformBuffer, mesh_allocator: &MeshAllocator, - mesh_material_ids: &RenderMeshMaterialIds, + mesh_material_ids: &RenderMaterialInstances, render_material_bindings: &RenderMaterialBindings, render_lightmaps: &RenderLightmaps, skin_uniforms: &SkinUniforms, @@ -1163,12 +1117,17 @@ impl RenderMeshInstanceGpuBuilder { // yet loaded. In that case, add the mesh to // `meshes_to_reextract_next_frame` and bail. let mesh_material = mesh_material_ids.mesh_material(entity); - let mesh_material_binding_id = match render_material_bindings.get(&mesh_material) { - Some(binding_id) => *binding_id, - None => { - meshes_to_reextract_next_frame.insert(entity); - return None; + let mesh_material_binding_id = if mesh_material != DUMMY_MESH_MATERIAL.untyped() { + match render_material_bindings.get(&mesh_material) { + Some(binding_id) => *binding_id, + None => { + meshes_to_reextract_next_frame.insert(entity); + return None; + } } + } else { + // Use a dummy material binding ID. + MaterialBindingId::default() }; self.shared.material_bindings_index = mesh_material_binding_id; @@ -1321,7 +1280,11 @@ pub struct RenderMeshQueueData<'a> { /// A [`SystemSet`] that encompasses both [`extract_meshes_for_cpu_building`] /// and [`extract_meshes_for_gpu_building`]. #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] -pub struct ExtractMeshesSet; +pub struct MeshExtractionSystems; + +/// Deprecated alias for [`MeshExtractionSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `MeshExtractionSystems`.")] +pub type ExtractMeshesSet = MeshExtractionSystems; /// Extracts meshes from the main world into the render world, populating the /// [`RenderMeshInstances`]. @@ -1330,6 +1293,8 @@ pub struct ExtractMeshesSet; /// [`MeshUniform`] building. pub fn extract_meshes_for_cpu_building( mut render_mesh_instances: ResMut, + mesh_material_ids: Res, + render_material_bindings: Res, render_visibility_ranges: Res, mut render_mesh_instance_queues: Local>>, meshes_query: Extract< @@ -1383,10 +1348,18 @@ pub fn extract_meshes_for_cpu_building( transmitted_receiver, ); - let shared = RenderMeshInstanceShared::from_components( + let mesh_material = mesh_material_ids.mesh_material(MainEntity::from(entity)); + + let material_bindings_index = render_material_bindings + .get(&mesh_material) + .copied() + .unwrap_or_default(); + + let shared = RenderMeshInstanceShared::for_cpu_building( previous_transform, mesh, tag, + material_bindings_index, not_shadow_caster, no_automatic_batching, ); @@ -1591,7 +1564,7 @@ fn extract_mesh_for_gpu_building( transmitted_receiver, ); - let shared = RenderMeshInstanceShared::from_components( + let shared = RenderMeshInstanceShared::for_gpu_building( previous_transform, mesh, tag, @@ -1643,7 +1616,7 @@ fn extract_mesh_for_gpu_building( /// [`crate::material::queue_material_meshes`] check the skin and morph target /// tables for each mesh, but that would be too slow in the hot mesh queuing /// loop. -fn set_mesh_motion_vector_flags( +pub(crate) fn set_mesh_motion_vector_flags( mut render_mesh_instances: ResMut, skin_uniforms: Res, morph_indices: Res, @@ -1668,7 +1641,7 @@ pub fn collect_meshes_for_gpu_building( mut mesh_culling_data_buffer: ResMut, mut render_mesh_instance_queues: ResMut, mesh_allocator: Res, - mesh_material_ids: Res, + mesh_material_ids: Res, render_material_bindings: Res, render_lightmaps: Res, skin_uniforms: Res, @@ -1778,6 +1751,8 @@ pub struct MeshPipeline { pub dummy_white_gpu_image: GpuImage, pub clustered_forward_buffer_binding_type: BufferBindingType, pub mesh_layouts: MeshLayouts, + /// The shader asset handle. + pub shader: Handle, /// `MeshUniform`s are stored in arrays in buffers. If storage buffers are available, they /// are used and this will be `None`, otherwise uniform buffers will be used with batches /// of this many `MeshUniform`s, stored at dynamic offsets within the uniform buffer. @@ -1807,6 +1782,7 @@ pub struct MeshPipeline { impl FromWorld for MeshPipeline { fn from_world(world: &mut World) -> Self { + let shader = load_embedded_asset!(world, "mesh.wgsl"); let mut system_state: SystemState<( Res, Res, @@ -1859,13 +1835,14 @@ impl FromWorld for MeshPipeline { clustered_forward_buffer_binding_type, dummy_white_gpu_image, mesh_layouts: MeshLayouts::new(&render_device, &render_adapter), + shader, per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter), clustered_decals_are_usable: decal::clustered::clustered_decals_are_usable( &render_device, &render_adapter, ), - skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device), + skins_use_uniform_buffers: skins_use_uniform_buffers(&render_device), } } } @@ -2593,13 +2570,13 @@ impl SpecializedMeshPipeline for MeshPipeline { Ok(RenderPipelineDescriptor { vertex: VertexState { - shader: MESH_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_buffer_layout], }, fragment: Some(FragmentState { - shader: MESH_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -3004,7 +2981,7 @@ impl RenderCommand

for SetMeshBindGroup { offset_count += 1; } if let Some(current_skin_index) = current_skin_byte_offset { - if skin::skins_use_uniform_buffers(&render_device) { + if skins_use_uniform_buffers(&render_device) { dynamic_offsets[offset_count] = current_skin_index.byte_offset; offset_count += 1; } @@ -3017,7 +2994,7 @@ impl RenderCommand

for SetMeshBindGroup { // Attach motion vectors if needed. if has_motion_vector_prepass { // Attach the previous skin index for motion vector computation. - if skin::skins_use_uniform_buffers(&render_device) { + if skins_use_uniform_buffers(&render_device) { if let Some(current_skin_byte_offset) = current_skin_byte_offset { dynamic_offsets[offset_count] = current_skin_byte_offset.byte_offset; offset_count += 1; @@ -3052,6 +3029,7 @@ impl RenderCommand

for DrawMesh { SRes, SRes, Option>, + SRes, ); type ViewQuery = Has; type ItemQuery = (); @@ -3067,6 +3045,7 @@ impl RenderCommand

for DrawMesh { pipeline_cache, mesh_allocator, preprocess_pipelines, + preprocessing_support, ): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -3075,7 +3054,8 @@ impl RenderCommand

for DrawMesh { // it's compiled. Otherwise, our mesh instance data won't be present. if let Some(preprocess_pipelines) = preprocess_pipelines { if !has_preprocess_bind_group - || !preprocess_pipelines.pipelines_are_loaded(&pipeline_cache) + || !preprocess_pipelines + .pipelines_are_loaded(&pipeline_cache, &preprocessing_support) { return RenderCommandResult::Skip; } diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 4b0a20ebec..6a29823022 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -13,4 +13,5 @@ pub use light::*; pub use mesh::*; pub use mesh_bindings::MeshLayouts; pub use mesh_view_bindings::*; -pub use skin::{extract_skins, prepare_skins, SkinUniforms, MAX_JOINTS}; +pub use morph::*; +pub use skin::{extract_skins, prepare_skins, skins_use_uniform_buffers, SkinUniforms, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 4b1ed68ce8..29070724dd 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -14,7 +14,7 @@ use bytemuck::NoUninit; #[derive(Component)] pub struct MorphIndex { - pub(super) index: u32, + pub index: u32, } /// Maps each mesh affected by morph targets to the applicable offset within the diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index d5cd3b03c1..fac7b97265 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -17,32 +17,24 @@ struct StandardMaterialBindings { normal_map_sampler: u32, // 10 depth_map_texture: u32, // 11 depth_map_sampler: u32, // 12 -#ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED anisotropy_texture: u32, // 13 anisotropy_sampler: u32, // 14 -#endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED -#ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED specular_transmission_texture: u32, // 15 specular_transmission_sampler: u32, // 16 thickness_texture: u32, // 17 thickness_sampler: u32, // 18 diffuse_transmission_texture: u32, // 19 diffuse_transmission_sampler: u32, // 20 -#endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED -#ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED clearcoat_texture: u32, // 21 clearcoat_sampler: u32, // 22 clearcoat_roughness_texture: u32, // 23 clearcoat_roughness_sampler: u32, // 24 clearcoat_normal_texture: u32, // 25 clearcoat_normal_sampler: u32, // 26 -#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED -#ifdef PBR_SPECULAR_TEXTURES_SUPPORTED specular_texture: u32, // 27 specular_sampler: u32, // 28 specular_tint_texture: u32, // 29 specular_tint_sampler: u32, // 30 -#endif // PBR_SPECULAR_TEXTURES_SUPPORTED } @group(2) @binding(0) var material_indices: array; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index e9b4e1f1a8..dcda30ee79 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -384,9 +384,9 @@ fn apply_pbr_lighting( transmissive_lighting_input.clearcoat_strength = 0.0; #endif // STANDARD_MATERIAL_CLEARCOAT #ifdef STANDARD_MATERIAL_ANISOTROPY - lighting_input.anisotropy = in.anisotropy_strength; - lighting_input.Ta = in.anisotropy_T; - lighting_input.Ba = in.anisotropy_B; + transmissive_lighting_input.anisotropy = in.anisotropy_strength; + transmissive_lighting_input.Ta = in.anisotropy_T; + transmissive_lighting_input.Ba = in.anisotropy_B; #endif // STANDARD_MATERIAL_ANISOTROPY #endif // STANDARD_MATERIAL_DIFFUSE_TRANSMISSION diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 4497b567e9..01e09fe3b4 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -278,7 +278,23 @@ fn compute_specular_layer_values_for_point_light( // Representative Point Area Lights. // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 - let centerToRay = dot(light_to_frag, R) * R - light_to_frag; + var LtFdotR = dot(light_to_frag, R); + + // HACK: the following line is an amendment to fix a discontinuity when a surface + // intersects the light sphere. See https://github.com/bevyengine/bevy/issues/13318 + // + // This sentence in the reference is crux of the problem: "We approximate finding the point with the + // smallest angle to the reflection ray by finding the point with the smallest distance to the ray." + // This approximation turns out to be completely wrong for points inside or near the sphere. + // Clamping this dot product to be positive ensures `centerToRay` lies on ray and not behind it. + // Any non-zero epsilon works here, it just has to be positive to avoid a singularity at zero. + // However, this is still far from physically accurate. Deriving an exact solution would help, + // but really we should adopt a superior solution to area lighting, such as: + // Physically Based Area Lights by Michal Drobot, or + // Polygonal-Light Shading with Linearly Transformed Cosines by Eric Heitz et al. + LtFdotR = max(0.0001, LtFdotR); + + let centerToRay = LtFdotR * R - light_to_frag; let closestPoint = light_to_frag + centerToRay * saturate( light_position_radius * inverseSqrt(dot(centerToRay, centerToRay))); let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint)); diff --git a/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl index 568f3821db..d2d2c71e64 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl @@ -25,8 +25,10 @@ fn prepass_alpha_discard(in: VertexOutput) { #ifdef BINDLESS let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; var output_color: vec4 = pbr_bindings::material_array[material_indices[slot].material].base_color; + let flags = pbr_bindings::material_array[material_indices[slot].material].flags; #else // BINDLESS var output_color: vec4 = pbr_bindings::material.base_color; + let flags = pbr_bindings::material.flags; #endif // BINDLESS #ifdef VERTEX_UVS @@ -38,10 +40,8 @@ fn prepass_alpha_discard(in: VertexOutput) { #ifdef BINDLESS let uv_transform = pbr_bindings::material_array[material_indices[slot].material].uv_transform; - let flags = pbr_bindings::material_array[material_indices[slot].material].flags; #else // BINDLESS let uv_transform = pbr_bindings::material.uv_transform; - let flags = pbr_bindings::material.flags; #endif // BINDLESS uv = (uv_transform * vec3(uv, 1.0)).xy; diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index bec846a038..476e06c1e7 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -4,7 +4,7 @@ use std::sync::OnceLock; use bevy_asset::{prelude::AssetChanged, Assets}; use bevy_ecs::prelude::*; use bevy_math::Mat4; -use bevy_platform_support::collections::hash_map::Entry; +use bevy_platform::collections::hash_map::Entry; use bevy_render::render_resource::{Buffer, BufferDescriptor}; use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; use bevy_render::{ @@ -211,7 +211,7 @@ pub fn prepare_skins( // Swap current and previous buffers. mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer); - // Resize the buffer if necessary. Include extra space equal to `MAX_JOINTS` + // Resize the buffers if necessary. Include extra space equal to `MAX_JOINTS` // because we need to be able to bind a full uniform buffer's worth of data // if skins use uniform buffers on this platform. let needed_size = (uniform.current_staging_buffer.len() as u64 + MAX_JOINTS as u64) @@ -223,7 +223,7 @@ pub fn prepare_skins( new_size += new_size / 2; } - // Create a new buffer. + // Create the new buffers. let buffer_usages = if skins_use_uniform_buffers(&render_device) { BufferUsages::UNIFORM } else { @@ -235,6 +235,24 @@ pub fn prepare_skins( size: new_size, mapped_at_creation: false, }); + uniform.prev_buffer = render_device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + usage: buffer_usages, + size: new_size, + mapped_at_creation: false, + }); + + // We've created a new `prev_buffer` but we don't have the previous joint + // data needed to fill it out correctly. Use the current joint data + // instead. + // + // TODO: This is a bug - will cause motion blur to ignore joint movement + // for one frame. + render_queue.write_buffer( + &uniform.prev_buffer, + 0, + bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]), + ); } // Write the data from `uniform.current_staging_buffer` into diff --git a/crates/bevy_pbr/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl index 981e5e1b1d..3873ffa3dd 100644 --- a/crates/bevy_pbr/src/render/wireframe.wgsl +++ b/crates/bevy_pbr/src/render/wireframe.wgsl @@ -1,12 +1,12 @@ #import bevy_pbr::forward_io::VertexOutput -struct WireframeMaterial { - color: vec4, -}; +struct PushConstants { + color: vec4 +} + +var push_constants: PushConstants; -@group(2) @binding(0) -var material: WireframeMaterial; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return material.color; + return push_constants.color; } diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 7a2a1fc264..dc3ea865f2 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -1,6 +1,6 @@ use crate::NodePbr; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, prelude::Camera3d, @@ -20,6 +20,7 @@ use bevy_render::{ camera::{ExtractedCamera, TemporalJitter}, extract_component::ExtractComponent, globals::{GlobalsBuffer, GlobalsUniform}, + load_shader_library, prelude::Camera, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_resource::{ @@ -33,44 +34,22 @@ use bevy_render::{ sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_utils::prelude::default; use core::mem; use tracing::{error, warn}; -const PREPROCESS_DEPTH_SHADER_HANDLE: Handle = - weak_handle!("b7f2cc3d-c935-4f5c-9ae2-43d6b0d5659a"); -const SSAO_SHADER_HANDLE: Handle = weak_handle!("9ea355d7-37a2-4cc4-b4d1-5d8ab47b07f5"); -const SPATIAL_DENOISE_SHADER_HANDLE: Handle = - weak_handle!("0f2764a0-b343-471b-b7ce-ef5d636f4fc3"); -const SSAO_UTILS_SHADER_HANDLE: Handle = - weak_handle!("da53c78d-f318-473e-bdff-b388bc50ada2"); - /// Plugin for screen space ambient occlusion. pub struct ScreenSpaceAmbientOcclusionPlugin; impl Plugin for ScreenSpaceAmbientOcclusionPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - PREPROCESS_DEPTH_SHADER_HANDLE, - "preprocess_depth.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, SSAO_SHADER_HANDLE, "ssao.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - SPATIAL_DENOISE_SHADER_HANDLE, - "spatial_denoise.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - SSAO_UTILS_SHADER_HANDLE, - "ssao_utils.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "ssao_utils.wgsl"); + + embedded_asset!(app, "preprocess_depth.wgsl"); + embedded_asset!(app, "ssao.wgsl"); + embedded_asset!(app, "spatial_denoise.wgsl"); app.register_type::(); @@ -111,9 +90,9 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { .add_systems( Render, ( - prepare_ssao_pipelines.in_set(RenderSet::Prepare), - prepare_ssao_textures.in_set(RenderSet::PrepareResources), - prepare_ssao_bind_groups.in_set(RenderSet::PrepareBindGroups), + prepare_ssao_pipelines.in_set(RenderSystems::Prepare), + prepare_ssao_textures.in_set(RenderSystems::PrepareResources), + prepare_ssao_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) .add_render_graph_node::>( @@ -146,7 +125,7 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { /// Requires that you add [`ScreenSpaceAmbientOcclusionPlugin`] to your app. /// /// It strongly recommended that you use SSAO in conjunction with -/// TAA ([`bevy_core_pipeline::experimental::taa::TemporalAntiAliasing`]). +/// TAA (`TemporalAntiAliasing`). /// Doing so greatly reduces SSAO noise. /// /// SSAO is not supported on `WebGL2`, and is not currently supported on `WebGPU`. @@ -321,6 +300,8 @@ struct SsaoPipelines { hilbert_index_lut: TextureView, point_clamp_sampler: Sampler, linear_clamp_sampler: Sampler, + + shader: Handle, } impl FromWorld for SsaoPipelines { @@ -431,7 +412,7 @@ impl FromWorld for SsaoPipelines { common_bind_group_layout.clone(), ], push_constant_ranges: vec![], - shader: PREPROCESS_DEPTH_SHADER_HANDLE, + shader: load_embedded_asset!(world, "preprocess_depth.wgsl"), shader_defs: Vec::new(), entry_point: "preprocess_depth".into(), zero_initialize_workgroup_memory: false, @@ -445,7 +426,7 @@ impl FromWorld for SsaoPipelines { common_bind_group_layout.clone(), ], push_constant_ranges: vec![], - shader: SPATIAL_DENOISE_SHADER_HANDLE, + shader: load_embedded_asset!(world, "spatial_denoise.wgsl"), shader_defs: Vec::new(), entry_point: "spatial_denoise".into(), zero_initialize_workgroup_memory: false, @@ -463,6 +444,8 @@ impl FromWorld for SsaoPipelines { hilbert_index_lut, point_clamp_sampler, linear_clamp_sampler, + + shader: load_embedded_asset!(world, "ssao.wgsl"), } } } @@ -498,7 +481,7 @@ impl SpecializedComputePipeline for SsaoPipelines { self.common_bind_group_layout.clone(), ], push_constant_ranges: vec![], - shader: SSAO_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "ssao".into(), zero_initialize_workgroup_memory: false, diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 1ee73da8f0..9f7dbb2f76 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -1,7 +1,7 @@ //! Screen space reflections implemented via raymarching. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::{ graph::{Core3d, Node3d}, @@ -23,7 +23,6 @@ use bevy_ecs::{ }; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::render_graph::RenderGraph; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, @@ -37,8 +36,9 @@ use bevy_render::{ }, renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue}, view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; +use bevy_render::{load_shader_library, render_graph::RenderGraph}; use bevy_utils::{once, prelude::default}; use tracing::info; @@ -49,9 +49,6 @@ use crate::{ ViewLightsUniformOffset, }; -const SSR_SHADER_HANDLE: Handle = weak_handle!("0b559df2-0d61-4f53-bf62-aea16cf32787"); -const RAYMARCH_SHADER_HANDLE: Handle = weak_handle!("798cc6fc-6072-4b6c-ab4f-83905fa4a19e"); - /// Enables screen-space reflections for a camera. /// /// Screen-space reflections are currently only supported with deferred rendering. @@ -158,6 +155,7 @@ pub struct ScreenSpaceReflectionsPipeline { depth_nearest_sampler: Sampler, bind_group_layout: BindGroupLayout, binding_arrays_are_usable: bool, + shader: Handle, } /// A GPU buffer that stores the screen space reflection settings for each view. @@ -179,13 +177,8 @@ pub struct ScreenSpaceReflectionsPipelineKey { impl Plugin for ScreenSpaceReflectionsPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, SSR_SHADER_HANDLE, "ssr.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - RAYMARCH_SHADER_HANDLE, - "raymarch.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "ssr.wgsl"); + load_shader_library!(app, "raymarch.wgsl"); app.register_type::() .add_plugins(ExtractComponentPlugin::::default()); @@ -196,10 +189,10 @@ impl Plugin for ScreenSpaceReflectionsPlugin { render_app .init_resource::() - .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSet::Prepare)) + .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare)) .add_systems( Render, - prepare_ssr_settings.in_set(RenderSet::PrepareResources), + prepare_ssr_settings.in_set(RenderSystems::PrepareResources), ) .add_render_graph_node::>( Core3d, @@ -404,6 +397,9 @@ impl FromWorld for ScreenSpaceReflectionsPipeline { depth_nearest_sampler, bind_group_layout, binding_arrays_are_usable: binding_arrays_are_usable(render_device, render_adapter), + // Even though ssr was loaded using load_shader_library, we can still access it like a + // normal embedded asset (so we can use it as both a library or a kernel). + shader: load_embedded_asset!(world, "ssr.wgsl"), } } } @@ -542,7 +538,7 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { layout: vec![mesh_view_layout.clone(), self.bind_group_layout.clone()], vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(), fragment: Some(FragmentState { - shader: SSR_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index b9f1d60945..efc3c5370a 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -30,7 +30,7 @@ //! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Assets, Handle}; +use bevy_asset::{embedded_asset, Assets, Handle}; use bevy_color::Color; use bevy_core_pipeline::core_3d::{ graph::{Core3d, Node3d}, @@ -48,15 +48,14 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ mesh::{Mesh, Meshable}, render_graph::{RenderGraphApp, ViewNodeRunner}, - render_resource::{Shader, SpecializedRenderPipelines}, + render_resource::SpecializedRenderPipelines, sync_component::SyncComponentPlugin, view::Visibility, - ExtractSchedule, Render, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::components::Transform; use render::{ VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH, - VOLUMETRIC_FOG_HANDLE, }; use crate::graph::NodePbr; @@ -189,12 +188,7 @@ pub struct FogVolume { impl Plugin for VolumetricFogPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - VOLUMETRIC_FOG_HANDLE, - "volumetric_fog.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "volumetric_fog.wgsl"); let mut meshes = app.world_mut().resource_mut::>(); meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into()); @@ -216,10 +210,10 @@ impl Plugin for VolumetricFogPlugin { .add_systems( Render, ( - render::prepare_volumetric_fog_pipelines.in_set(RenderSet::Prepare), - render::prepare_volumetric_fog_uniforms.in_set(RenderSet::Prepare), + render::prepare_volumetric_fog_pipelines.in_set(RenderSystems::Prepare), + render::prepare_volumetric_fog_uniforms.in_set(RenderSystems::Prepare), render::prepare_view_depth_textures_for_volumetric_fog - .in_set(RenderSet::Prepare) + .in_set(RenderSystems::Prepare) .before(prepare_core_3d_depth_textures), ), ); diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 07012a72e2..45f694e546 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -2,7 +2,7 @@ use core::array; -use bevy_asset::{weak_handle, AssetId, Handle}; +use bevy_asset::{load_embedded_asset, weak_handle, AssetId, Handle}; use bevy_color::ColorToComponents as _; use bevy_core_pipeline::{ core_3d::Camera3d, @@ -77,10 +77,6 @@ bitflags! { } } -/// The volumetric fog shader. -pub const VOLUMETRIC_FOG_HANDLE: Handle = - weak_handle!("481f474c-2024-44bb-8f79-f7c05ced95ea"); - /// The plane mesh, which is used to render a fog volume that the camera is /// inside. /// @@ -121,6 +117,9 @@ pub struct VolumetricFogPipeline { /// /// Since there aren't too many of these, we precompile them all. volumetric_view_bind_group_layouts: [BindGroupLayout; VOLUMETRIC_FOG_BIND_GROUP_LAYOUT_COUNT], + + // The shader asset handle. + shader: Handle, } /// The two render pipelines that we use for fog volumes: one for when a 3D @@ -266,6 +265,7 @@ impl FromWorld for VolumetricFogPipeline { VolumetricFogPipeline { mesh_view_layouts: mesh_view_layouts.clone(), volumetric_view_bind_group_layouts: bind_group_layouts, + shader: load_embedded_asset!(world, "volumetric_fog.wgsl"), } } } @@ -564,7 +564,7 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline { layout: vec![mesh_view_layout.clone(), volumetric_view_bind_group_layout], push_constant_ranges: vec![], vertex: VertexState { - shader: VOLUMETRIC_FOG_HANDLE, + shader: self.shader.clone(), shader_defs: shader_defs.clone(), entry_point: "vertex".into(), buffers: vec![vertex_format], @@ -576,7 +576,7 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline { depth_stencil: None, multisample: MultisampleState::default(), fragment: Some(FragmentState { - shader: VOLUMETRIC_FOG_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 88082c2880..cc64ad2e4f 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,18 +1,60 @@ -use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d}; -use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; -use bevy_ecs::prelude::*; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{ - extract_resource::ExtractResource, - mesh::{Mesh3d, MeshVertexBufferLayoutRef}, - prelude::*, - render_resource::*, +use crate::{ + DrawMesh, MeshPipeline, MeshPipelineKey, RenderMeshInstanceFlags, RenderMeshInstances, + SetMeshBindGroup, SetMeshViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; - -pub const WIREFRAME_SHADER_HANDLE: Handle = - weak_handle!("2646a633-f8e3-4380-87ae-b44d881abbce"); +use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; +use bevy_asset::{ + embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp, + AssetEventSystems, AssetId, Assets, Handle, UntypedAssetId, +}; +use bevy_color::{Color, ColorToComponents}; +use bevy_core_pipeline::core_3d::{ + graph::{Core3d, Node3d}, + Camera3d, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Tick, + prelude::*, + query::QueryItem, + system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, +}; +use bevy_platform::{ + collections::{HashMap, HashSet}, + hash::FixedHasher, +}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::camera::extract_cameras; +use bevy_render::{ + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, + camera::ExtractedCamera, + extract_resource::ExtractResource, + mesh::{ + allocator::{MeshAllocator, SlabId}, + Mesh3d, MeshVertexBufferLayoutRef, RenderMesh, + }, + prelude::*, + render_asset::{ + prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, + }, + render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, + render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, + PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + }, + render_resource::*, + renderer::RenderContext, + sync_world::{MainEntity, MainEntityHashMap}, + view::{ + ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, + RetainedViewEntity, ViewDepthTexture, ViewTarget, + }, + Extract, Render, RenderApp, RenderDebugFlags, RenderSystems, +}; +use core::{hash::Hash, ops::Range}; +use tracing::error; /// A [`Plugin`] that draws wireframes. /// @@ -24,35 +66,99 @@ pub const WIREFRAME_SHADER_HANDLE: Handle = /// /// This is a native only feature. #[derive(Debug, Default)] -pub struct WireframePlugin; +pub struct WireframePlugin { + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, +} + +impl WireframePlugin { + /// Creates a new [`WireframePlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { debug_flags } + } +} + impl Plugin for WireframePlugin { - fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( - app, - WIREFRAME_SHADER_HANDLE, - "render/wireframe.wgsl", - Shader::from_wgsl + fn build(&self, app: &mut App) { + embedded_asset!(app, "render/wireframe.wgsl"); + + app.add_plugins(( + BinnedRenderPhasePlugin::::new(self.debug_flags), + RenderAssetPlugin::::default(), + )) + .init_asset::() + .init_resource::>() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .init_resource::() + .add_systems(Startup, setup_global_wireframe_material) + .add_systems( + Update, + ( + global_color_changed.run_if(resource_changed::), + wireframe_color_changed, + // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global + // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. + (apply_wireframe_material, apply_global_wireframe_material).chain(), + ), + ) + .add_systems( + PostUpdate, + check_wireframe_entities_needing_specialization + .after(AssetEventSystems) + .run_if(resource_exists::), ); - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_plugins(MaterialPlugin::::default()) - .register_asset_reflect::() - .add_systems(Startup, setup_global_wireframe_material) - .add_systems( - Update, + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_render_graph_node::>(Core3d, Node3d::Wireframe) + .add_render_graph_edges( + Core3d, ( - global_color_changed.run_if(resource_changed::), - wireframe_color_changed, - // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global - // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. - (apply_wireframe_material, apply_global_wireframe_material).chain(), + Node3d::EndMainPass, + Node3d::Wireframe, + Node3d::PostProcessing, + ), + ) + .add_systems( + ExtractSchedule, + ( + extract_wireframe_3d_camera, + extract_wireframe_entities_needing_specialization.after(extract_cameras), + extract_wireframe_materials, + ), + ) + .add_systems( + Render, + ( + specialize_wireframes + .in_set(RenderSystems::PrepareMeshes) + .after(prepare_assets::) + .after(prepare_assets::), + queue_wireframes + .in_set(RenderSystems::QueueMeshes) + .after(prepare_assets::), ), ); } + + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app.init_resource::(); + } } /// Enables wireframe rendering for any entity it is attached to. @@ -60,34 +166,274 @@ impl Plugin for WireframePlugin { /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe; +pub struct Wireframe3d { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: Wireframe3dBatchSetKey, + /// The key, which determines which can be batched. + pub bin_key: Wireframe3dBinKey, + /// An entity from which data will be fetched, including the mesh if + /// applicable. + pub representative_entity: (Entity, MainEntity), + /// The ranges of instances. + pub batch_range: Range, + /// An extra index, which is either a dynamic offset or an index in the + /// indirect parameters list. + pub extra_index: PhaseItemExtraIndex, +} + +impl PhaseItem for Wireframe3d { + fn entity(&self) -> Entity { + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 + } + + fn draw_function(&self) -> DrawFunctionId { + self.batch_set_key.draw_function + } + + fn batch_range(&self) -> &Range { + &self.batch_range + } + + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + fn extra_index(&self) -> PhaseItemExtraIndex { + self.extra_index.clone() + } + + fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { + (&mut self.batch_range, &mut self.extra_index) + } +} + +impl CachedRenderPipelinePhaseItem for Wireframe3d { + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.batch_set_key.pipeline + } +} + +impl BinnedPhaseItem for Wireframe3d { + type BinKey = Wireframe3dBinKey; + type BatchSetKey = Wireframe3dBatchSetKey; + + fn new( + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, + representative_entity: (Entity, MainEntity), + batch_range: Range, + extra_index: PhaseItemExtraIndex, + ) -> Self { + Self { + batch_set_key, + bin_key, + representative_entity, + batch_range, + extra_index, + } + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe3dBatchSetKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + + /// The wireframe material asset ID. + pub asset_id: UntypedAssetId, + + /// The function used to draw. + pub draw_function: DrawFunctionId, + /// The ID of the slab of GPU memory that contains vertex data. + /// + /// For non-mesh items, you can fill this with 0 if your items can be + /// multi-drawn, or with a unique value if they can't. + pub vertex_slab: SlabId, + + /// The ID of the slab of GPU memory that contains index data, if present. + /// + /// For non-mesh items, you can safely fill this with `None`. + pub index_slab: Option, +} + +impl PhaseItemBatchSetKey for Wireframe3dBatchSetKey { + fn indexed(&self) -> bool { + self.index_slab.is_some() + } +} + +/// Data that must be identical in order to *batch* phase items together. +/// +/// Note that a *batch set* (if multi-draw is in use) contains multiple batches. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe3dBinKey { + /// The wireframe mesh asset ID. + pub asset_id: UntypedAssetId, +} + +pub struct SetWireframe3dPushConstants; + +impl RenderCommand

for SetWireframe3dPushConstants { + type Param = ( + SRes, + SRes>, + ); + type ViewQuery = (); + type ItemQuery = (); + + #[inline] + fn render<'w>( + item: &P, + _view: (), + _item_query: Option<()>, + (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + + pass.set_push_constants( + ShaderStages::FRAGMENT, + 0, + bytemuck::bytes_of(&wireframe_material.color), + ); + RenderCommandResult::Success + } +} + +pub type DrawWireframe3d = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + SetWireframe3dPushConstants, + DrawMesh, +); + +#[derive(Resource, Clone)] +pub struct Wireframe3dPipeline { + mesh_pipeline: MeshPipeline, + shader: Handle, +} + +impl FromWorld for Wireframe3dPipeline { + fn from_world(render_world: &mut World) -> Self { + Wireframe3dPipeline { + mesh_pipeline: render_world.resource::().clone(), + shader: load_embedded_asset!(render_world, "render/wireframe.wgsl"), + } + } +} + +impl SpecializedMeshPipeline for Wireframe3dPipeline { + type Key = MeshPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + descriptor.label = Some("wireframe_3d_pipeline".into()); + descriptor.push_constant_ranges.push(PushConstantRange { + stages: ShaderStages::FRAGMENT, + range: 0..16, + }); + let fragment = descriptor.fragment.as_mut().unwrap(); + fragment.shader = self.shader.clone(); + descriptor.primitive.polygon_mode = PolygonMode::Line; + descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; + Ok(descriptor) + } +} + +#[derive(Default)] +struct Wireframe3dNode; +impl ViewNode for Wireframe3dNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewTarget, + &'static ViewDepthTexture, + ); + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let Some(wireframe_phase) = world.get_resource::>() + else { + return Ok(()); + }; + + let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { + return Ok(()); + }; + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_3d_pass"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { + error!("Error encountered while rendering the stencil phase {err:?}"); + return Err(NodeRunError::DrawError(err)); + } + + Ok(()) + } +} + /// Sets the color of the [`Wireframe`] of the entity it is attached to. /// /// If this component is present but there's no [`Wireframe`] component, /// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true. /// /// This overrides the [`WireframeConfig::default_color`]. -// TODO: consider caching materials based on this color. -// This could blow up in size if people use random colored wireframes for each mesh. -// It will also be important to remove unused materials from the cache. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default, Debug, Clone)] +#[reflect(Component, Default, Debug)] pub struct WireframeColor { pub color: Color, } +#[derive(Component, Debug, Clone, Default)] +pub struct ExtractedWireframeColor { + pub color: [f32; 4], +} + /// Disables wireframe rendering for any entity it is attached to. /// It will ignore the [`WireframeConfig`] global setting. /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource, Debug, Default, Clone)] +#[reflect(Resource, Debug, Default)] pub struct WireframeConfig { /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component. @@ -98,12 +444,112 @@ pub struct WireframeConfig { pub default_color: Color, } +#[derive(Asset, Reflect, Clone, Debug, Default)] +#[reflect(Clone, Default)] +pub struct WireframeMaterial { + pub color: Color, +} + +pub struct RenderWireframeMaterial { + pub color: [f32; 4], +} + +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)] +#[reflect(Component, Default, Clone, PartialEq)] +pub struct Mesh3dWireframe(pub Handle); + +impl AsAssetId for Mesh3dWireframe { + type Asset = WireframeMaterial; + + fn as_asset_id(&self) -> AssetId { + self.0.id() + } +} + +impl RenderAsset for RenderWireframeMaterial { + type SourceAsset = WireframeMaterial; + type Param = (); + + fn prepare_asset( + source_asset: Self::SourceAsset, + _asset_id: AssetId, + _param: &mut SystemParamItem, + ) -> Result> { + Ok(RenderWireframeMaterial { + color: source_asset.color.to_linear().to_f32_array(), + }) + } +} + +#[derive(Resource, Deref, DerefMut, Default)] +pub struct RenderWireframeInstances(MainEntityHashMap>); + +#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] +pub struct WireframeEntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, +} + +#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)] +pub struct WireframeEntitySpecializationTicks { + pub entities: MainEntityHashMap, +} + +/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut, Default)] +pub struct SpecializedWireframePipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap, +} + +/// Stores the cached render pipeline ID for each entity in a single view, as +/// well as the last time it was changed. +#[derive(Deref, DerefMut, Default)] +pub struct SpecializedWireframeViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, +} + #[derive(Resource)] struct GlobalWireframeMaterial { // This handle will be reused when the global config is enabled handle: Handle, } +pub fn extract_wireframe_materials( + mut material_instances: ResMut, + changed_meshes_query: Extract< + Query< + (Entity, &ViewVisibility, &Mesh3dWireframe), + Or<(Changed, Changed)>, + >, + >, + mut removed_visibilities_query: Extract>, + mut removed_materials_query: Extract>, +) { + for (entity, view_visibility, material) in &changed_meshes_query { + if view_visibility.get() { + material_instances.insert(entity.into(), material.id()); + } else { + material_instances.remove(&MainEntity::from(entity)); + } + } + + for entity in removed_visibilities_query + .read() + .chain(removed_materials_query.read()) + { + // Only queue a mesh for removal if we didn't pick it up above. + // It's possible that a necessary component was removed and re-added in + // the same frame. + if !changed_meshes_query.contains(entity) { + material_instances.remove(&MainEntity::from(entity)); + } + } +} + fn setup_global_wireframe_material( mut commands: Commands, mut materials: ResMut>, @@ -112,7 +558,7 @@ fn setup_global_wireframe_material( // Create the handle used for the global material commands.insert_resource(GlobalWireframeMaterial { handle: materials.add(WireframeMaterial { - color: config.default_color.into(), + color: config.default_color, }), }); } @@ -124,7 +570,7 @@ fn global_color_changed( global_material: Res, ) { if let Some(global_material) = materials.get_mut(&global_material.handle) { - global_material.color = config.default_color.into(); + global_material.color = config.default_color; } } @@ -132,13 +578,13 @@ fn global_color_changed( fn wireframe_color_changed( mut materials: ResMut>, mut colors_changed: Query< - (&mut MeshMaterial3d, &WireframeColor), + (&mut Mesh3dWireframe, &WireframeColor), (With, Changed), >, ) { for (mut handle, wireframe_color) in &mut colors_changed { handle.0 = materials.add(WireframeMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }); } } @@ -150,22 +596,22 @@ fn apply_wireframe_material( mut materials: ResMut>, wireframes: Query< (Entity, Option<&WireframeColor>), - (With, Without>), + (With, Without), >, - no_wireframes: Query, With>)>, + no_wireframes: Query, With)>, mut removed_wireframes: RemovedComponents, global_material: Res, ) { for e in removed_wireframes.read().chain(no_wireframes.iter()) { if let Ok(mut commands) = commands.get_entity(e) { - commands.remove::>(); + commands.remove::(); } } let mut material_to_spawn = vec![]; for (e, maybe_color) in &wireframes { let material = get_wireframe_material(maybe_color, &mut materials, &global_material); - material_to_spawn.push((e, MeshMaterial3d(material))); + material_to_spawn.push((e, Mesh3dWireframe(material))); } commands.try_insert_batch(material_to_spawn); } @@ -178,12 +624,9 @@ fn apply_global_wireframe_material( config: Res, meshes_without_material: Query< (Entity, Option<&WireframeColor>), - (WireframeFilter, Without>), - >, - meshes_with_global_material: Query< - Entity, - (WireframeFilter, With>), + (WireframeFilter, Without), >, + meshes_with_global_material: Query)>, global_material: Res, mut materials: ResMut>, ) { @@ -193,14 +636,12 @@ fn apply_global_wireframe_material( let material = get_wireframe_material(maybe_color, &mut materials, &global_material); // We only add the material handle but not the Wireframe component // This makes it easy to detect which mesh is using the global material and which ones are user specified - material_to_spawn.push((e, MeshMaterial3d(material))); + material_to_spawn.push((e, Mesh3dWireframe(material))); } commands.try_insert_batch(material_to_spawn); } else { for e in &meshes_with_global_material { - commands - .entity(e) - .remove::>(); + commands.entity(e).remove::(); } } } @@ -213,7 +654,7 @@ fn get_wireframe_material( ) -> Handle { if let Some(wireframe_color) = maybe_color { wireframe_materials.add(WireframeMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }) } else { // If there's no color specified we can use the global material since it's already set to use the default_color @@ -221,28 +662,241 @@ fn get_wireframe_material( } } -#[derive(Default, AsBindGroup, Debug, Clone, Asset, Reflect)] -#[reflect(Default, Clone)] -pub struct WireframeMaterial { - #[uniform(0)] - pub color: LinearRgba, -} - -impl Material for WireframeMaterial { - fn fragment_shader() -> ShaderRef { - WIREFRAME_SHADER_HANDLE.into() - } - - fn specialize( - _pipeline: &MaterialPipeline, - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - _key: MaterialPipelineKey, - ) -> Result<(), SpecializedMeshPipelineError> { - descriptor.primitive.polygon_mode = PolygonMode::Line; - if let Some(depth_stencil) = descriptor.depth_stencil.as_mut() { - depth_stencil.bias.slope_scale = 1.0; +fn extract_wireframe_3d_camera( + mut wireframe_3d_phases: ResMut>, + cameras: Extract), With>>, + mut live_entities: Local>, + gpu_preprocessing_support: Res, +) { + live_entities.clear(); + for (main_entity, camera, no_indirect_drawing) in &cameras { + if !camera.is_active { + continue; + } + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); + + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + wireframe_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); + live_entities.insert(retained_view_entity); + } + + // Clear out all dead views. + wireframe_3d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); +} + +pub fn extract_wireframe_entities_needing_specialization( + entities_needing_specialization: Extract>, + mut entity_specialization_ticks: ResMut, + views: Query<&ExtractedView>, + mut specialized_wireframe_pipeline_cache: ResMut, + mut removed_meshes_query: Extract>, + ticks: SystemChangeTick, +) { + for entity in entities_needing_specialization.iter() { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); + } + + for entity in removed_meshes_query.read() { + for view in &views { + if let Some(specialized_wireframe_pipeline_cache) = + specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity) + { + specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity)); + } + } + } +} + +pub fn check_wireframe_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed, + AssetChanged, + )>, + >, + mut entities_needing_specialization: ResMut, +) { + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } +} + +pub fn specialize_wireframes( + render_meshes: Res>, + render_mesh_instances: Res, + render_wireframe_instances: Res, + render_visibility_ranges: Res, + wireframe_phases: Res>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res, + entity_specialization_ticks: Res, + view_specialization_ticks: Res, + mut specialized_material_pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + pipeline_cache: Res, + ticks: SystemChangeTick, +) { + // Record the retained IDs of all views so that we can expire old + // pipeline IDs. + let mut all_views: HashSet = HashSet::default(); + + for (view, visible_entities) in &views { + all_views.insert(view.retained_view_entity); + + if !wireframe_phases.contains_key(&view.retained_view_entity) { + continue; + } + + let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else { + continue; + }; + + let view_tick = view_specialization_ticks + .get(&view.retained_view_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(view.retained_view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter::() { + if !render_wireframe_instances.contains_key(visible_entity) { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); + let last_specialized_tick = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(tick, _)| *tick); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); + if !needs_specialization { + continue; + } + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + + let mut mesh_key = *view_key; + mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); + + if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { + mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; + } + + if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + // If the previous frame have skins or morph targets, note that. + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; + } + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; + } + } + + let pipeline_id = + pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } + + // Delete specialized pipelines belonging to views that have expired. + specialized_material_pipeline_cache + .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); +} + +fn queue_wireframes( + custom_draw_functions: Res>, + render_mesh_instances: Res, + gpu_preprocessing_support: Res, + mesh_allocator: Res, + specialized_wireframe_pipeline_cache: Res, + render_wireframe_instances: Res, + mut wireframe_3d_phases: ResMut>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, +) { + for (view, visible_entities) in &mut views { + let Some(wireframe_phase) = wireframe_3d_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let draw_wireframe = custom_draw_functions.read().id::(); + + let Some(view_specialized_material_pipeline_cache) = + specialized_wireframe_pipeline_cache.get(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + continue; + }; + let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { + continue; + } + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) + else { + continue; + }; + let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let bin_key = Wireframe3dBinKey { + asset_id: mesh_instance.mesh_asset_id.untyped(), + }; + let batch_set_key = Wireframe3dBatchSetKey { + pipeline: pipeline_id, + asset_id: wireframe_instance.untyped(), + draw_function: draw_wireframe, + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + wireframe_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + mesh_instance.current_uniform_index, + BinnedRenderPhaseType::mesh( + mesh_instance.should_batch(), + &gpu_preprocessing_support, + ), + current_change_tick, + ); } - Ok(()) } } diff --git a/crates/bevy_picking/Cargo.toml b/crates/bevy_picking/Cargo.toml index 95db895ac0..f02e5237aa 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -26,7 +26,7 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_picking/src/backend.rs b/crates/bevy_picking/src/backend.rs index 6c0db34e72..9e28cc6d7c 100644 --- a/crates/bevy_picking/src/backend.rs +++ b/crates/bevy_picking/src/backend.rs @@ -42,7 +42,7 @@ pub mod prelude { pub use super::{ray::RayMap, HitData, PointerHits}; pub use crate::{ pointer::{PointerId, PointerLocation}, - PickSet, Pickable, + Pickable, PickingSystems, }; } @@ -54,7 +54,7 @@ pub mod prelude { /// /// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering /// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered -/// against [`PickSet::Backend`](crate::PickSet::Backend), or better, avoid reading `PointerHits` in `PreUpdate`. +/// against [`PickingSystems::Backend`](crate::PickingSystems::Backend), or better, avoid reading `PointerHits` in `PreUpdate`. #[derive(Event, Debug, Clone, Reflect)] #[reflect(Debug, Clone)] pub struct PointerHits { @@ -84,7 +84,7 @@ pub struct PointerHits { } impl PointerHits { - #[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] + /// Construct [`PointerHits`]. pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self { Self { pointer, @@ -114,7 +114,7 @@ pub struct HitData { } impl HitData { - #[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] + /// Construct a [`HitData`]. pub fn new(camera: Entity, depth: f32, position: Option, normal: Option) -> Self { Self { camera, @@ -131,7 +131,7 @@ pub mod ray { use crate::backend::prelude::{PointerId, PointerLocation}; use bevy_ecs::prelude::*; use bevy_math::Ray3d; - use bevy_platform_support::collections::{hash_map::Iter, HashMap}; + use bevy_platform::collections::{hash_map::Iter, HashMap}; use bevy_reflect::Reflect; use bevy_render::camera::Camera; use bevy_transform::prelude::GlobalTransform; @@ -178,7 +178,10 @@ pub mod ray { /// ``` #[derive(Clone, Debug, Default, Resource)] pub struct RayMap { - map: HashMap, + /// Cartesian product of all pointers and all cameras + /// Add your rays here to support picking through indirections, + /// e.g. rendered-to-texture cameras + pub map: HashMap, } impl RayMap { @@ -187,11 +190,6 @@ pub mod ray { self.map.iter() } - /// The hash map of all rays cast in the current frame. - pub fn map(&self) -> &HashMap { - &self.map - } - /// Clears the [`RayMap`] and re-populates it with one ray for each /// combination of pointer entity and camera entity where the pointer /// intersects the camera's viewport. @@ -231,11 +229,8 @@ pub mod ray { if !pointer_loc.is_in_viewport(camera, primary_window_entity) { return None; } - let mut viewport_pos = pointer_loc.position; - if let Some(viewport) = &camera.viewport { - let viewport_logical = camera.to_logical(viewport.physical_position)?; - viewport_pos -= viewport_logical; - } - camera.viewport_to_world(camera_tfm, viewport_pos).ok() + camera + .viewport_to_world(camera_tfm, pointer_loc.position) + .ok() } } diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 16240700af..9a5bc51bab 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -23,7 +23,7 @@ //! //! The order in which interaction events are received is extremely important, and you can read more //! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in -//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Hover`](crate::PickSet::Hover). All pointer-event +//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickingSystems::Hover`](crate::PickingSystems::Hover). All pointer-event //! observers resolve during the sync point between [`pointer_events`] and //! [`update_interactions`](crate::hover::update_interactions). //! @@ -42,8 +42,8 @@ use core::{fmt::Debug, time::Duration}; use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal}; use bevy_input::mouse::MouseScrollUnit; use bevy_math::Vec2; -use bevy_platform_support::collections::HashMap; -use bevy_platform_support::time::Instant; +use bevy_platform::collections::HashMap; +use bevy_platform::time::Instant; use bevy_reflect::prelude::*; use bevy_render::camera::NormalizedRenderTarget; use bevy_window::Window; @@ -92,7 +92,7 @@ where // Send event to parent, if it has one. if let Some(child_of) = child_of { - return Some(child_of.parent); + return Some(child_of.parent()); }; // Otherwise, send it to the window entity (unless this is a window entity). @@ -152,7 +152,7 @@ pub struct Cancel { pub hit: HitData, } -/// Fires when a the pointer crosses into the bounds of the `target` entity. +/// Fires when a pointer crosses into the bounds of the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] #[reflect(Clone, PartialEq)] pub struct Over { @@ -160,7 +160,7 @@ pub struct Over { pub hit: HitData, } -/// Fires when a the pointer crosses out of the bounds of the `target` entity. +/// Fires when a pointer crosses out of the bounds of the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] #[reflect(Clone, PartialEq)] pub struct Out { @@ -527,11 +527,7 @@ pub fn pointer_events( for button in PointerButton::iter() { let state = pointer_state.get_mut(pointer_id, button); - for drag_target in state - .dragging - .keys() - .filter(|&&drag_target| hovered_entity != drag_target) - { + for drag_target in state.dragging.keys() { state.dragging_over.insert(hovered_entity, hit.clone()); let drag_enter_event = Pointer::new( pointer_id, diff --git a/crates/bevy_picking/src/hover.rs b/crates/bevy_picking/src/hover.rs index 5e5e715890..6347568c02 100644 --- a/crates/bevy_picking/src/hover.rs +++ b/crates/bevy_picking/src/hover.rs @@ -16,7 +16,7 @@ use crate::{ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::FloatOrd; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::prelude::*; type DepthSortedHits = Vec<(Entity, HitData)>; @@ -131,9 +131,7 @@ fn build_over_map( .filter(|e| !cancelled_pointers.contains(&e.pointer)) { let pointer = entities_under_pointer.pointer; - let layer_map = pointer_over_map - .entry(pointer) - .or_insert_with(BTreeMap::new); + let layer_map = pointer_over_map.entry(pointer).or_default(); for (entity, pick_data) in entities_under_pointer.picks.iter() { let layer = entities_under_pointer.order; let hits = layer_map.entry(FloatOrd(layer)).or_default(); diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index ce87df95b4..d751e07c94 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -20,7 +20,7 @@ use bevy_input::{ ButtonState, }; use bevy_math::Vec2; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::prelude::*; use bevy_render::camera::RenderTarget; use bevy_window::{PrimaryWindow, WindowEvent, WindowRef}; @@ -30,7 +30,7 @@ use crate::pointer::{ Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation, }; -use crate::PickSet; +use crate::PickingSystems; /// The picking input prelude. /// @@ -86,7 +86,7 @@ impl Plugin for PointerInputPlugin { touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled), ) .chain() - .in_set(PickSet::Input), + .in_set(PickingSystems::Input), ) .add_systems( Last, diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 17e8c994b1..53387e84c8 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -179,7 +179,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::mesh_picking::{ ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastBackfaces, RayCastVisibility}, - MeshPickingPlugin, MeshPickingSettings, RayCastPickable, + MeshPickingCamera, MeshPickingPlugin, MeshPickingSettings, }; #[doc(hidden)] pub use crate::{ @@ -256,7 +256,7 @@ impl Default for Pickable { /// Groups the stages of the picking process under shared labels. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum PickSet { +pub enum PickingSystems { /// Produces pointer input events. In the [`First`] schedule. Input, /// Runs after input events are generated but before commands are flushed. In the [`First`] @@ -269,13 +269,17 @@ pub enum PickSet { /// Reads [`backend::PointerHits`]s, and updates the hovermap, selection, and highlighting states. In /// the [`PreUpdate`] schedule. Hover, - /// Runs after all the [`PickSet::Hover`] systems are done, before event listeners are triggered. In the + /// Runs after all the [`PickingSystems::Hover`] systems are done, before event listeners are triggered. In the /// [`PreUpdate`] schedule. PostHover, /// Runs after all other picking sets. In the [`PreUpdate`] schedule. Last, } +/// Deprecated alias for [`PickingSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `PickingSystems`.")] +pub type PickSet = PickingSystems; + /// One plugin that contains the [`PointerInputPlugin`](input::PointerInputPlugin), [`PickingPlugin`] /// and the [`InteractionPlugin`], this is probably the plugin that will be most used. /// @@ -359,29 +363,29 @@ impl Plugin for PickingPlugin { pointer::PointerInput::receive, backend::ray::RayMap::repopulate.after(pointer::PointerInput::receive), ) - .in_set(PickSet::ProcessInput), + .in_set(PickingSystems::ProcessInput), ) .add_systems( PreUpdate, window::update_window_hits .run_if(Self::window_picking_should_run) - .in_set(PickSet::Backend), + .in_set(PickingSystems::Backend), ) .configure_sets( First, - (PickSet::Input, PickSet::PostInput) - .after(bevy_time::TimeSystem) - .after(bevy_ecs::event::EventUpdates) + (PickingSystems::Input, PickingSystems::PostInput) + .after(bevy_time::TimeSystems) + .after(bevy_ecs::event::EventUpdateSystems) .chain(), ) .configure_sets( PreUpdate, ( - PickSet::ProcessInput.run_if(Self::input_should_run), - PickSet::Backend, - PickSet::Hover.run_if(Self::hover_should_run), - PickSet::PostHover, - PickSet::Last, + PickingSystems::ProcessInput.run_if(Self::input_should_run), + PickingSystems::Backend, + PickingSystems::Hover.run_if(Self::hover_should_run), + PickingSystems::PostHover, + PickingSystems::Last, ) .chain(), ) @@ -427,7 +431,7 @@ impl Plugin for InteractionPlugin { PreUpdate, (generate_hovermap, update_interactions, pointer_events) .chain() - .in_set(PickSet::Hover), + .in_set(PickingSystems::Hover), ); } } diff --git a/crates/bevy_picking/src/mesh_picking/mod.rs b/crates/bevy_picking/src/mesh_picking/mod.rs index ceccc12764..8e6a16690c 100644 --- a/crates/bevy_picking/src/mesh_picking/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/mod.rs @@ -1,10 +1,11 @@ //! A [mesh ray casting](ray_cast) backend for [`bevy_picking`](crate). //! -//! By default, all meshes are pickable. Picking can be disabled for individual entities -//! by adding [`Pickable::IGNORE`]. +//! By default, all meshes that have [`bevy_asset::RenderAssetUsages::MAIN_WORLD`] are pickable. +//! Picking can be disabled for individual entities by adding [`Pickable::IGNORE`]. //! //! To make mesh picking entirely opt-in, set [`MeshPickingSettings::require_markers`] -//! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities. +//! to `true` and add [`MeshPickingCamera`] and [`Pickable`] components to the desired camera and +//! target entities. //! //! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter. //! @@ -18,7 +19,7 @@ pub mod ray_cast; use crate::{ backend::{ray::RayMap, HitData, PointerHits}, prelude::*, - PickSet, + PickingSystems, }; use bevy_app::prelude::*; use bevy_ecs::prelude::*; @@ -26,12 +27,19 @@ use bevy_reflect::prelude::*; use bevy_render::{prelude::*, view::RenderLayers}; use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility, SimplifiedMesh}; +/// An optional component that marks cameras that should be used in the [`MeshPickingPlugin`]. +/// +/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise. +#[derive(Debug, Clone, Default, Component, Reflect)] +#[reflect(Debug, Default, Component)] +pub struct MeshPickingCamera; + /// Runtime settings for the [`MeshPickingPlugin`]. #[derive(Resource, Reflect)] #[reflect(Resource, Default)] pub struct MeshPickingSettings { - /// When set to `true` ray casting will only happen between cameras and entities marked with - /// [`RayCastPickable`]. `false` by default. + /// When set to `true` ray casting will only consider cameras marked with + /// [`MeshPickingCamera`] and entities marked with [`Pickable`]. `false` by default. /// /// This setting is provided to give you fine-grained control over which cameras and entities /// should be used by the mesh picking backend at runtime. @@ -54,12 +62,6 @@ impl Default for MeshPickingSettings { } } -/// An optional component that marks cameras and target entities that should be used in the [`MeshPickingPlugin`]. -/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise. -#[derive(Debug, Clone, Default, Component, Reflect)] -#[reflect(Component, Default, Clone)] -pub struct RayCastPickable; - /// Adds the mesh picking backend to your app. #[derive(Clone, Default)] pub struct MeshPickingPlugin; @@ -67,10 +69,9 @@ pub struct MeshPickingPlugin; impl Plugin for MeshPickingPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::() .register_type::() .register_type::() - .add_systems(PreUpdate, update_hits.in_set(PickSet::Backend)); + .add_systems(PreUpdate, update_hits.in_set(PickingSystems::Backend)); } } @@ -78,18 +79,18 @@ impl Plugin for MeshPickingPlugin { pub fn update_hits( backend_settings: Res, ray_map: Res, - picking_cameras: Query<(&Camera, Option<&RayCastPickable>, Option<&RenderLayers>)>, + picking_cameras: Query<(&Camera, Has, Option<&RenderLayers>)>, pickables: Query<&Pickable>, - marked_targets: Query<&RayCastPickable>, + marked_targets: Query<&Pickable>, layers: Query<&RenderLayers>, mut ray_cast: MeshRayCast, mut output: EventWriter, ) { - for (&ray_id, &ray) in ray_map.map().iter() { - let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else { + for (&ray_id, &ray) in ray_map.iter() { + let Ok((camera, cam_can_pick, cam_layers)) = picking_cameras.get(ray_id.camera) else { continue; }; - if backend_settings.require_markers && cam_pickable.is_none() { + if backend_settings.require_markers && !cam_can_pick { continue; } diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index 7b15fe7121..9988a96e19 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -1,6 +1,6 @@ use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A}; +use bevy_mesh::{Indices, Mesh, PrimitiveTopology}; use bevy_reflect::Reflect; -use bevy_render::mesh::{Indices, Mesh, PrimitiveTopology}; use super::Backfaces; @@ -83,9 +83,10 @@ pub fn ray_mesh_intersection + Clone + Copy>( indices .chunks_exact(3) + .enumerate() .fold( (f32::MAX, None), - |(closest_distance, closest_hit), triangle| { + |(closest_distance, closest_hit), (tri_idx, triangle)| { let [Ok(a), Ok(b), Ok(c)] = [ triangle[0].try_into(), triangle[1].try_into(), @@ -104,7 +105,7 @@ pub fn ray_mesh_intersection + Clone + Copy>( match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) { Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => { - (hit.distance, Some((a, hit))) + (hit.distance, Some((tri_idx, hit))) } _ => (closest_distance, closest_hit), } @@ -188,7 +189,7 @@ pub fn ray_mesh_intersection + Clone + Copy>( .transform_vector3(ray.direction * hit.distance) .length(), triangle: Some(tri_vertices.map(|v| mesh_transform.transform_point3(v))), - triangle_index: Some(a), + triangle_index: Some(tri_idx), }) }) } diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index 2eec8c86b7..c1f465b96a 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -7,8 +7,8 @@ mod intersections; use bevy_derive::{Deref, DerefMut}; use bevy_math::{bounding::Aabb3d, Ray3d}; +use bevy_mesh::Mesh; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::mesh::Mesh; use intersections::*; pub use intersections::{ray_aabb_intersection_3d, ray_mesh_intersection, RayMeshHit}; diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index c061065d74..faba90cbb9 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -11,7 +11,7 @@ use bevy_ecs::prelude::*; use bevy_input::mouse::MouseScrollUnit; use bevy_math::Vec2; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::prelude::*; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_window::PrimaryWindow; @@ -205,8 +205,8 @@ impl PointerLocation { /// - a pointer is not associated with a [`Camera`] because multiple cameras can target the same /// render target. It is up to picking backends to associate a Pointer's `Location` with a /// specific `Camera`, if any. -#[derive(Debug, Clone, Component, Reflect, PartialEq)] -#[reflect(Component, Debug, PartialEq, Clone)] +#[derive(Debug, Clone, Reflect, PartialEq)] +#[reflect(Debug, PartialEq, Clone)] pub struct Location { /// The [`NormalizedRenderTarget`] associated with the pointer, usually a window. pub target: NormalizedRenderTarget, diff --git a/crates/bevy_platform_support/Cargo.toml b/crates/bevy_platform/Cargo.toml similarity index 77% rename from crates/bevy_platform_support/Cargo.toml rename to crates/bevy_platform/Cargo.toml index 75883a697c..44c680394d 100644 --- a/crates/bevy_platform_support/Cargo.toml +++ b/crates/bevy_platform/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "bevy_platform_support" +name = "bevy_platform" version = "0.16.0-dev" edition = "2024" -description = "Platform compatibility support for Bevy Engine" +description = "Provides common platform agnostic APIs, as well as platform-specific features for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" @@ -14,7 +14,10 @@ default = ["std"] # Functionality ## Adds serialization support through `serde`. -serialize = ["hashbrown/serde"] +serialize = ["dep:serde", "hashbrown/serde"] + +## Adds integration with Rayon. +rayon = ["dep:rayon", "hashbrown/rayon"] # Platform Compatibility @@ -28,10 +31,11 @@ std = [ "portable-atomic-util/std", "spin/std", "foldhash/std", + "serde?/std", ] ## Allows access to the `alloc` crate. -alloc = ["portable-atomic-util/alloc", "dep:hashbrown"] +alloc = ["portable-atomic-util/alloc", "dep:hashbrown", "serde?/alloc"] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. @@ -42,9 +46,8 @@ critical-section = ["dep:critical-section", "portable-atomic/critical-section"] web = ["dep:web-time", "dep:getrandom"] [dependencies] -cfg-if = "1.0.0" critical-section = { version = "1.2.0", default-features = false, optional = true } -spin = { version = "0.9.8", default-features = false, features = [ +spin = { version = "0.10.0", default-features = false, features = [ "mutex", "spin_mutex", "rwlock", @@ -57,6 +60,8 @@ hashbrown = { version = "0.15.1", features = [ "equivalent", "raw-entry", ], optional = true, default-features = false } +serde = { version = "1", default-features = false, optional = true } +rayon = { version = "1", default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-time = { version = "1.1", default-features = false, optional = true } @@ -68,8 +73,8 @@ getrandom = { version = "0.2.0", default-features = false, optional = true, feat portable-atomic = { version = "1", default-features = false, features = [ "fallback", ] } -spin = { version = "0.9.8", default-features = false, features = [ - "portable_atomic", +spin = { version = "0.10.0", default-features = false, features = [ + "portable-atomic", ] } [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies] diff --git a/crates/bevy_platform/LICENSE-APACHE b/crates/bevy_platform/LICENSE-APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/crates/bevy_platform/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_platform/LICENSE-MIT b/crates/bevy_platform/LICENSE-MIT new file mode 100644 index 0000000000..9cf106272a --- /dev/null +++ b/crates/bevy_platform/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_platform_support/README.md b/crates/bevy_platform/README.md similarity index 72% rename from crates/bevy_platform_support/README.md rename to crates/bevy_platform/README.md index 69969ad614..4d853751aa 100644 --- a/crates/bevy_platform_support/README.md +++ b/crates/bevy_platform/README.md @@ -1,16 +1,16 @@ # Bevy Platform Support [![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) -[![Crates.io](https://img.shields.io/crates/v/bevy_platform_support.svg)](https://crates.io/crates/bevy_platform_support) -[![Downloads](https://img.shields.io/crates/d/bevy_platform_support.svg)](https://crates.io/crates/bevy_platform_support) -[![Docs](https://docs.rs/bevy_platform_support/badge.svg)](https://docs.rs/bevy_platform_support/latest/bevy_platform_support/) +[![Crates.io](https://img.shields.io/crates/v/bevy_platform.svg)](https://crates.io/crates/bevy_platform) +[![Downloads](https://img.shields.io/crates/d/bevy_platform.svg)](https://crates.io/crates/bevy_platform) +[![Docs](https://docs.rs/bevy_platform/badge.svg)](https://docs.rs/bevy_platform/latest/bevy_platform/) [![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) Rust is a fantastic multi-platform language with extensive support for modern targets through its [standard library](https://doc.rust-lang.org/stable/std/). However, some items within the standard library have alternatives that are better suited for [Bevy](https://crates.io/crates/bevy) and game engines in general. Additionally, to support embedded and other esoteric platforms, it's often necessary to shed reliance on `std`, making your crate [`no_std`](https://docs.rust-embedded.org/book/intro/no-std.html). -These needs are handled by this crate, `bevy_platform_support`. +These needs are handled by this crate, `bevy_platform`. The goal of this crate is to provide alternatives and extensions to the Rust standard library which minimize friction when developing with and for Bevy across multiple platforms. ## Getting Started @@ -18,19 +18,19 @@ The goal of this crate is to provide alternatives and extensions to the Rust sta Like any dependency from [crates.io](https://crates.io/), use `cargo` to add it to your `Cargo.toml` file: ```sh -cargo add bevy_platform_support +cargo add bevy_platform ``` -Now, instead of importing from `std` you can use `bevy_platform_support` for items it has alternative for. +Now, instead of importing from `std` you can use `bevy_platform` for items it has alternative for. See the documentation for what items are available, and explanations for _why_ you may want to use them. ## `no_std` Support -By default, `bevy_platform_support` will activate the `std` feature, requiring access to the `std` crate for whichever platforms you're targeting. +By default, `bevy_platform` will activate the `std` feature, requiring access to the `std` crate for whichever platforms you're targeting. To use this crate on `no_std` platforms, disable default features: ```toml -bevy_platform_support = { version = "x.y.z", default-features = false } +bevy_platform = { version = "x.y.z", default-features = false } ``` ## Features diff --git a/crates/bevy_platform/src/cell/mod.rs b/crates/bevy_platform/src/cell/mod.rs new file mode 100644 index 0000000000..04f8bf6572 --- /dev/null +++ b/crates/bevy_platform/src/cell/mod.rs @@ -0,0 +1,9 @@ +//! Provides cell primitives. +//! +//! This is a drop-in replacement for `std::cell::SyncCell`/`std::cell::SyncUnsafeCell`. + +mod sync_cell; +mod sync_unsafe_cell; + +pub use sync_cell::SyncCell; +pub use sync_unsafe_cell::SyncUnsafeCell; diff --git a/crates/bevy_utils/src/synccell.rs b/crates/bevy_platform/src/cell/sync_cell.rs similarity index 100% rename from crates/bevy_utils/src/synccell.rs rename to crates/bevy_platform/src/cell/sync_cell.rs diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_platform/src/cell/sync_unsafe_cell.rs similarity index 98% rename from crates/bevy_utils/src/syncunsafecell.rs rename to crates/bevy_platform/src/cell/sync_unsafe_cell.rs index 104256969d..bb67557ec2 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_platform/src/cell/sync_unsafe_cell.rs @@ -94,7 +94,7 @@ impl SyncUnsafeCell<[T]> { /// # Examples /// /// ``` - /// # use bevy_utils::syncunsafecell::SyncUnsafeCell; + /// # use bevy_platform::cell::SyncUnsafeCell; /// /// let slice: &mut [i32] = &mut [1, 2, 3]; /// let cell_slice: &SyncUnsafeCell<[i32]> = SyncUnsafeCell::from_mut(slice); diff --git a/crates/bevy_platform/src/cfg.rs b/crates/bevy_platform/src/cfg.rs new file mode 100644 index 0000000000..6f86ce1873 --- /dev/null +++ b/crates/bevy_platform/src/cfg.rs @@ -0,0 +1,264 @@ +//! Provides helpful configuration macros, allowing detection of platform features such as +//! [`alloc`](crate::cfg::alloc) or [`std`](crate::cfg::std) without explicit features. + +/// Provides a `match`-like expression similar to [`cfg_if`] and based on the experimental +/// [`cfg_match`]. +/// The name `switch` is used to avoid conflict with the `match` keyword. +/// Arms are evaluated top to bottom, and an optional wildcard arm can be provided if no match +/// can be made. +/// +/// An arm can either be: +/// - a `cfg(...)` pattern (e.g., `feature = "foo"`) +/// - a wildcard `_` +/// - an alias defined using [`define_alias`] +/// +/// Common aliases are provided by [`cfg`](crate::cfg). +/// Note that aliases are evaluated from the context of the defining crate, not the consumer. +/// +/// # Examples +/// +/// ``` +/// # use bevy_platform::cfg; +/// # fn log(_: &str) {} +/// # fn foo(_: &str) {} +/// # +/// cfg::switch! { +/// #[cfg(feature = "foo")] => { +/// foo("We have the `foo` feature!") +/// } +/// cfg::std => { +/// extern crate std; +/// std::println!("No `foo`, but we have `std`!"); +/// } +/// _ => { +/// log("Don't have `std` or `foo`"); +/// } +/// } +/// ``` +/// +/// [`cfg_if`]: https://crates.io/crates/cfg-if +/// [`cfg_match`]: https://github.com/rust-lang/rust/issues/115585 +#[doc(inline)] +pub use crate::switch; + +/// Defines an alias for a particular configuration. +/// This has two advantages over directly using `#[cfg(...)]`: +/// +/// 1. Complex configurations can be abbreviated to more meaningful shorthand. +/// 2. Features are evaluated in the context of the _defining_ crate, not the consuming. +/// +/// The second advantage is a particularly powerful tool, as it allows consuming crates to use +/// functionality in a defining crate regardless of what crate in the dependency graph enabled the +/// relevant feature. +/// +/// For example, consider a crate `foo` that depends on another crate `bar`. +/// `bar` has a feature "`faster_algorithms`". +/// If `bar` defines a "`faster_algorithms`" alias: +/// +/// ```ignore +/// define_alias! { +/// #[cfg(feature = "faster_algorithms")] => { faster_algorithms } +/// } +/// ``` +/// +/// Now, `foo` can gate its usage of those faster algorithms on the alias, avoiding the need to +/// expose its own "`faster_algorithms`" feature. +/// This also avoids the unfortunate situation where one crate activates "`faster_algorithms`" on +/// `bar` without activating that same feature on `foo`. +/// +/// Once an alias is defined, there are 4 ways you can use it: +/// +/// 1. Evaluate with no contents to return a `bool` indicating if the alias is active. +/// ``` +/// # use bevy_platform::cfg; +/// if cfg::std!() { +/// // Have `std`! +/// } else { +/// // No `std`... +/// } +/// ``` +/// 2. Pass a single code block which will only be compiled if the alias is active. +/// ``` +/// # use bevy_platform::cfg; +/// cfg::std! { +/// // Have `std`! +/// # () +/// } +/// ``` +/// 3. Pass a single `if { ... } else { ... }` expression to conditionally compile either the first +/// or the second code block. +/// ``` +/// # use bevy_platform::cfg; +/// cfg::std! { +/// if { +/// // Have `std`! +/// } else { +/// // No `std`... +/// } +/// } +/// ``` +/// 4. Use in a [`switch`] arm for more complex conditional compilation. +/// ``` +/// # use bevy_platform::cfg; +/// cfg::switch! { +/// cfg::std => { +/// // Have `std`! +/// } +/// cfg::alloc => { +/// // No `std`, but do have `alloc`! +/// } +/// _ => { +/// // No `std` or `alloc`... +/// } +/// } +/// ``` +#[doc(inline)] +pub use crate::define_alias; + +/// Macro which represents an enabled compilation condition. +#[doc(inline)] +pub use crate::enabled; + +/// Macro which represents a disabled compilation condition. +#[doc(inline)] +pub use crate::disabled; + +#[doc(hidden)] +#[macro_export] +macro_rules! switch { + ({ $($tt:tt)* }) => {{ + $crate::switch! { $($tt)* } + }}; + (_ => { $($output:tt)* }) => { + $($output)* + }; + ( + $cond:path => $output:tt + $($( $rest:tt )+)? + ) => { + $cond! { + if { + $crate::switch! { _ => $output } + } else { + $( + $crate::switch! { $($rest)+ } + )? + } + } + }; + ( + #[cfg($cfg:meta)] => $output:tt + $($( $rest:tt )+)? + ) => { + #[cfg($cfg)] + $crate::switch! { _ => $output } + $( + #[cfg(not($cfg))] + $crate::switch! { $($rest)+ } + )? + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! disabled { + () => { false }; + (if { $($p:tt)* } else { $($n:tt)* }) => { $($n)* }; + ($($p:tt)*) => {}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! enabled { + () => { true }; + (if { $($p:tt)* } else { $($n:tt)* }) => { $($p)* }; + ($($p:tt)*) => { $($p)* }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! define_alias { + ( + #[cfg($meta:meta)] => $p:ident + $(, $( $rest:tt )+)? + ) => { + $crate::define_alias! { + #[cfg($meta)] => { $p } + $( + $($rest)+ + )? + } + }; + ( + #[cfg($meta:meta)] => $p:ident, + $($( $rest:tt )+)? + ) => { + $crate::define_alias! { + #[cfg($meta)] => { $p } + $( + $($rest)+ + )? + } + }; + ( + #[cfg($meta:meta)] => { + $(#[$p_meta:meta])* + $p:ident + } + $($( $rest:tt )+)? + ) => { + $crate::switch! { + #[cfg($meta)] => { + $(#[$p_meta])* + #[doc(inline)] + /// + #[doc = concat!("This macro passes the provided code because `#[cfg(", stringify!($meta), ")]` is currently active.")] + pub use $crate::enabled as $p; + } + _ => { + $(#[$p_meta])* + #[doc(inline)] + /// + #[doc = concat!("This macro suppresses the provided code because `#[cfg(", stringify!($meta), ")]` is _not_ currently active.")] + pub use $crate::disabled as $p; + } + } + + $( + $crate::define_alias! { + $($rest)+ + } + )? + } +} + +define_alias! { + #[cfg(feature = "alloc")] => { + /// Indicates the `alloc` crate is available and can be used. + alloc + } + #[cfg(feature = "std")] => { + /// Indicates the `std` crate is available and can be used. + std + } + #[cfg(panic = "unwind")] => { + /// Indicates that a [`panic`] will be unwound, and can be potentially caught. + panic_unwind + } + #[cfg(panic = "abort")] => { + /// Indicates that a [`panic`] will lead to an abort, and cannot be caught. + panic_abort + } + #[cfg(all(target_arch = "wasm32", feature = "web"))] => { + /// Indicates that this target has access to browser APIs. + web + } + #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] => { + /// Indicates that this target has access to a native implementation of `Arc`. + arc + } + #[cfg(feature = "critical-section")] => { + /// Indicates `critical-section` is available. + critical_section + } +} diff --git a/crates/bevy_platform/src/collections/hash_map.rs b/crates/bevy_platform/src/collections/hash_map.rs new file mode 100644 index 0000000000..ae978a7fce --- /dev/null +++ b/crates/bevy_platform/src/collections/hash_map.rs @@ -0,0 +1,1287 @@ +//! Provides [`HashMap`] based on [hashbrown]'s implementation. +//! Unlike [`hashbrown::HashMap`], [`HashMap`] defaults to [`FixedHasher`] +//! instead of [`RandomState`]. +//! This provides determinism by default with an acceptable compromise to denial +//! of service resistance in the context of a game engine. + +use core::{ + fmt::Debug, + hash::{BuildHasher, Hash}, + ops::{Deref, DerefMut, Index}, +}; + +use hashbrown::{hash_map as hb, Equivalent}; + +use crate::hash::FixedHasher; + +#[cfg(feature = "rayon")] +use rayon::prelude::{FromParallelIterator, IntoParallelIterator, ParallelExtend}; + +// Re-exports to match `std::collections::hash_map` +pub use { + crate::hash::{DefaultHasher, RandomState}, + hb::{ + Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, OccupiedEntry, VacantEntry, + Values, ValuesMut, + }, +}; + +// Additional items from `hashbrown` +pub use hb::{ + EntryRef, ExtractIf, OccupiedError, RawEntryBuilder, RawEntryBuilderMut, RawEntryMut, + RawOccupiedEntryMut, +}; + +/// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider. +pub type Entry<'a, K, V, S = FixedHasher> = hb::Entry<'a, K, V, S>; + +/// New-type for [`HashMap`](hb::HashMap) with [`FixedHasher`] as the default hashing provider. +/// Can be trivially converted to and from a [hashbrown] [`HashMap`](hb::HashMap) using [`From`]. +/// +/// A new-type is used instead of a type alias due to critical methods like [`new`](hb::HashMap::new) +/// being incompatible with Bevy's choice of default hasher. +#[repr(transparent)] +pub struct HashMap(hb::HashMap); + +impl Clone for HashMap +where + hb::HashMap: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Self(self.0.clone()) + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.0.clone_from(&source.0); + } +} + +impl Debug for HashMap +where + hb::HashMap: Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + as Debug>::fmt(&self.0, f) + } +} + +impl Default for HashMap +where + hb::HashMap: Default, +{ + #[inline] + fn default() -> Self { + Self(Default::default()) + } +} + +impl PartialEq for HashMap +where + hb::HashMap: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for HashMap where hb::HashMap: Eq {} + +impl FromIterator for HashMap +where + hb::HashMap: FromIterator, +{ + #[inline] + fn from_iter>(iter: U) -> Self { + Self(FromIterator::from_iter(iter)) + } +} + +impl Index for HashMap +where + hb::HashMap: Index, +{ + type Output = as Index>::Output; + + #[inline] + fn index(&self, index: T) -> &Self::Output { + self.0.index(index) + } +} + +impl IntoIterator for HashMap +where + hb::HashMap: IntoIterator, +{ + type Item = as IntoIterator>::Item; + + type IntoIter = as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a HashMap +where + &'a hb::HashMap: IntoIterator, +{ + type Item = <&'a hb::HashMap as IntoIterator>::Item; + + type IntoIter = <&'a hb::HashMap as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + (&self.0).into_iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut HashMap +where + &'a mut hb::HashMap: IntoIterator, +{ + type Item = <&'a mut hb::HashMap as IntoIterator>::Item; + + type IntoIter = <&'a mut hb::HashMap as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + (&mut self.0).into_iter() + } +} + +impl Extend for HashMap +where + hb::HashMap: Extend, +{ + #[inline] + fn extend>(&mut self, iter: U) { + self.0.extend(iter); + } +} + +impl From<[(K, V); N]> for HashMap +where + K: Eq + Hash, +{ + fn from(arr: [(K, V); N]) -> Self { + arr.into_iter().collect() + } +} + +impl From> for HashMap { + #[inline] + fn from(value: hb::HashMap) -> Self { + Self(value) + } +} + +impl From> for hb::HashMap { + #[inline] + fn from(value: HashMap) -> Self { + value.0 + } +} + +impl Deref for HashMap { + type Target = hb::HashMap; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HashMap { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(feature = "serialize")] +impl serde::Serialize for HashMap +where + hb::HashMap: serde::Serialize, +{ + #[inline] + fn serialize(&self, serializer: T) -> Result + where + T: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +#[cfg(feature = "serialize")] +impl<'de, K, V, S> serde::Deserialize<'de> for HashMap +where + hb::HashMap: serde::Deserialize<'de>, +{ + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self(serde::Deserialize::deserialize(deserializer)?)) + } +} + +#[cfg(feature = "rayon")] +impl FromParallelIterator for HashMap +where + hb::HashMap: FromParallelIterator, + T: Send, +{ + fn from_par_iter

(par_iter: P) -> Self + where + P: IntoParallelIterator, + { + Self( as FromParallelIterator>::from_par_iter(par_iter)) + } +} + +#[cfg(feature = "rayon")] +impl IntoParallelIterator for HashMap +where + hb::HashMap: IntoParallelIterator, +{ + type Item = as IntoParallelIterator>::Item; + type Iter = as IntoParallelIterator>::Iter; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "rayon")] +impl<'a, K: Sync, V: Sync, S> IntoParallelIterator for &'a HashMap +where + &'a hb::HashMap: IntoParallelIterator, +{ + type Item = <&'a hb::HashMap as IntoParallelIterator>::Item; + type Iter = <&'a hb::HashMap as IntoParallelIterator>::Iter; + + fn into_par_iter(self) -> Self::Iter { + (&self.0).into_par_iter() + } +} + +#[cfg(feature = "rayon")] +impl<'a, K: Sync, V: Sync, S> IntoParallelIterator for &'a mut HashMap +where + &'a mut hb::HashMap: IntoParallelIterator, +{ + type Item = <&'a mut hb::HashMap as IntoParallelIterator>::Item; + type Iter = <&'a mut hb::HashMap as IntoParallelIterator>::Iter; + + fn into_par_iter(self) -> Self::Iter { + (&mut self.0).into_par_iter() + } +} + +#[cfg(feature = "rayon")] +impl ParallelExtend for HashMap +where + hb::HashMap: ParallelExtend, + T: Send, +{ + fn par_extend(&mut self, par_iter: I) + where + I: IntoParallelIterator, + { + as ParallelExtend>::par_extend(&mut self.0, par_iter); + } +} + +impl HashMap { + /// Creates an empty [`HashMap`]. + /// + /// Refer to [`new`](hb::HashMap::new) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// // Creates a HashMap with zero capacity. + /// let map = HashMap::new(); + /// # + /// # let mut map = map; + /// # map.insert(0usize, "foo"); + /// # assert_eq!(map.get(&0), Some("foo").as_ref()); + /// ``` + #[inline] + pub const fn new() -> Self { + Self::with_hasher(FixedHasher) + } + + /// Creates an empty [`HashMap`] with the specified capacity. + /// + /// Refer to [`with_capacity`](hb::HashMap::with_capacity) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// // Creates a HashMap with capacity for at least 5 entries. + /// let map = HashMap::with_capacity(5); + /// # + /// # let mut map = map; + /// # map.insert(0usize, "foo"); + /// # assert_eq!(map.get(&0), Some("foo").as_ref()); + /// ``` + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self::with_capacity_and_hasher(capacity, FixedHasher) + } +} + +impl HashMap { + /// Creates an empty [`HashMap`] which will use the given hash builder to hash + /// keys. + /// + /// Refer to [`with_hasher`](hb::HashMap::with_hasher) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # use bevy_platform::hash::FixedHasher as SomeHasher; + /// // Creates a HashMap with the provided hasher. + /// let map = HashMap::with_hasher(SomeHasher); + /// # + /// # let mut map = map; + /// # map.insert(0usize, "foo"); + /// # assert_eq!(map.get(&0), Some("foo").as_ref()); + /// ``` + #[inline] + pub const fn with_hasher(hash_builder: S) -> Self { + Self(hb::HashMap::with_hasher(hash_builder)) + } + + /// Creates an empty [`HashMap`] with the specified capacity, using `hash_builder` + /// to hash the keys. + /// + /// Refer to [`with_capacity_and_hasher`](hb::HashMap::with_capacity_and_hasher) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # use bevy_platform::hash::FixedHasher as SomeHasher; + /// // Creates a HashMap with capacity for 5 entries and the provided hasher. + /// let map = HashMap::with_capacity_and_hasher(5, SomeHasher); + /// # + /// # let mut map = map; + /// # map.insert(0usize, "foo"); + /// # assert_eq!(map.get(&0), Some("foo").as_ref()); + /// ``` + #[inline] + pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self { + Self(hb::HashMap::with_capacity_and_hasher( + capacity, + hash_builder, + )) + } + + /// Returns a reference to the map's [`BuildHasher`], or `S` parameter. + /// + /// Refer to [`hasher`](hb::HashMap::hasher) for further details. + #[inline] + pub fn hasher(&self) -> &S { + self.0.hasher() + } + + /// Returns the number of elements the map can hold without reallocating. + /// + /// Refer to [`capacity`](hb::HashMap::capacity) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let map = HashMap::with_capacity(5); + /// + /// # let map: HashMap<(), ()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// ``` + #[inline] + pub fn capacity(&self) -> usize { + self.0.capacity() + } + + /// An iterator visiting all keys in arbitrary order. + /// The iterator element type is `&'a K`. + /// + /// Refer to [`keys`](hb::HashMap::keys) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for key in map.keys() { + /// // foo, bar, baz + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.keys().count(), 3); + /// ``` + #[inline] + pub fn keys(&self) -> Keys<'_, K, V> { + self.0.keys() + } + + /// An iterator visiting all values in arbitrary order. + /// The iterator element type is `&'a V`. + /// + /// Refer to [`values`](hb::HashMap::values) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for key in map.values() { + /// // 0, 1, 2 + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.values().count(), 3); + /// ``` + #[inline] + pub fn values(&self) -> Values<'_, K, V> { + self.0.values() + } + + /// An iterator visiting all values mutably in arbitrary order. + /// The iterator element type is `&'a mut V`. + /// + /// Refer to [`values`](hb::HashMap::values) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for key in map.values_mut() { + /// // 0, 1, 2 + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.values_mut().count(), 3); + /// ``` + #[inline] + pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { + self.0.values_mut() + } + + /// An iterator visiting all key-value pairs in arbitrary order. + /// The iterator element type is `(&'a K, &'a V)`. + /// + /// Refer to [`iter`](hb::HashMap::iter) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for (key, value) in map.iter() { + /// // ("foo", 0), ("bar", 1), ("baz", 2) + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.iter().count(), 3); + /// ``` + #[inline] + pub fn iter(&self) -> Iter<'_, K, V> { + self.0.iter() + } + + /// An iterator visiting all key-value pairs in arbitrary order, + /// with mutable references to the values. + /// The iterator element type is `(&'a K, &'a mut V)`. + /// + /// Refer to [`iter_mut`](hb::HashMap::iter_mut) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for (key, value) in map.iter_mut() { + /// // ("foo", 0), ("bar", 1), ("baz", 2) + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.iter_mut().count(), 3); + /// ``` + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + self.0.iter_mut() + } + + /// Returns the number of elements in the map. + /// + /// Refer to [`len`](hb::HashMap::len) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// assert_eq!(map.len(), 0); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.len(), 1); + /// ``` + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the map contains no elements. + /// + /// Refer to [`is_empty`](hb::HashMap::is_empty) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// assert!(map.is_empty()); + /// + /// map.insert("foo", 0); + /// + /// assert!(!map.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Clears the map, returning all key-value pairs as an iterator. Keeps the + /// allocated memory for reuse. + /// + /// Refer to [`drain`](hb::HashMap::drain) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for (key, value) in map.drain() { + /// // ("foo", 0), ("bar", 1), ("baz", 2) + /// // Note that the above order is not guaranteed + /// } + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn drain(&mut self) -> Drain<'_, K, V> { + self.0.drain() + } + + /// Retains only the elements specified by the predicate. Keeps the + /// allocated memory for reuse. + /// + /// Refer to [`retain`](hb::HashMap::retain) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// map.retain(|key, value| *value == 2); + /// + /// assert_eq!(map.len(), 1); + /// ``` + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&K, &mut V) -> bool, + { + self.0.retain(f); + } + + /// Drains elements which are true under the given predicate, + /// and returns an iterator over the removed items. + /// + /// Refer to [`extract_if`](hb::HashMap::extract_if) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// let extracted = map + /// .extract_if(|key, value| *value == 2) + /// .collect::>(); + /// + /// assert_eq!(map.len(), 2); + /// assert_eq!(extracted.len(), 1); + /// ``` + #[inline] + pub fn extract_if(&mut self, f: F) -> ExtractIf<'_, K, V, F> + where + F: FnMut(&K, &mut V) -> bool, + { + self.0.extract_if(f) + } + + /// Clears the map, removing all key-value pairs. Keeps the allocated memory + /// for reuse. + /// + /// Refer to [`clear`](hb::HashMap::clear) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// map.clear(); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Creates a consuming iterator visiting all the keys in arbitrary order. + /// The map cannot be used after calling this. + /// The iterator element type is `K`. + /// + /// Refer to [`into_keys`](hb::HashMap::into_keys) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for key in map.into_keys() { + /// // "foo", "bar", "baz" + /// // Note that the above order is not guaranteed + /// } + /// ``` + #[inline] + pub fn into_keys(self) -> IntoKeys { + self.0.into_keys() + } + + /// Creates a consuming iterator visiting all the values in arbitrary order. + /// The map cannot be used after calling this. + /// The iterator element type is `V`. + /// + /// Refer to [`into_values`](hb::HashMap::into_values) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// # + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// for key in map.into_values() { + /// // 0, 1, 2 + /// // Note that the above order is not guaranteed + /// } + /// ``` + #[inline] + pub fn into_values(self) -> IntoValues { + self.0.into_values() + } + + /// Takes the inner [`HashMap`](hb::HashMap) out of this wrapper. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let map: HashMap<&'static str, usize> = HashMap::new(); + /// let map: hashbrown::HashMap<&'static str, usize, _> = map.into_inner(); + /// ``` + #[inline] + pub fn into_inner(self) -> hb::HashMap { + self.0 + } +} + +impl HashMap +where + K: Eq + Hash, + S: BuildHasher, +{ + /// Reserves capacity for at least `additional` more elements to be inserted + /// in the [`HashMap`]. The collection may reserve more space to avoid + /// frequent reallocations. + /// + /// Refer to [`reserve`](hb::HashMap::reserve) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::with_capacity(5); + /// + /// # let mut map: HashMap<(), ()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// + /// map.reserve(10); + /// + /// assert!(map.capacity() - map.len() >= 10); + /// ``` + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.0.reserve(additional); + } + + /// Tries to reserve capacity for at least `additional` more elements to be inserted + /// in the given `HashMap`. The collection may reserve more space to avoid + /// frequent reallocations. + /// + /// Refer to [`try_reserve`](hb::HashMap::try_reserve) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::with_capacity(5); + /// + /// # let mut map: HashMap<(), ()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// + /// map.try_reserve(10).expect("Out of Memory!"); + /// + /// assert!(map.capacity() - map.len() >= 10); + /// ``` + #[inline] + pub fn try_reserve(&mut self, additional: usize) -> Result<(), hashbrown::TryReserveError> { + self.0.try_reserve(additional) + } + + /// Shrinks the capacity of the map as much as possible. It will drop + /// down as much as possible while maintaining the internal rules + /// and possibly leaving some space in accordance with the resize policy. + /// + /// Refer to [`shrink_to_fit`](hb::HashMap::shrink_to_fit) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::with_capacity(5); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// assert!(map.capacity() >= 5); + /// + /// map.shrink_to_fit(); + /// + /// assert_eq!(map.capacity(), 3); + /// ``` + #[inline] + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit(); + } + + /// Shrinks the capacity of the map with a lower limit. It will drop + /// down no lower than the supplied limit while maintaining the internal rules + /// and possibly leaving some space in accordance with the resize policy. + /// + /// Refer to [`shrink_to`](hb::HashMap::shrink_to) for further details. + #[inline] + pub fn shrink_to(&mut self, min_capacity: usize) { + self.0.shrink_to(min_capacity); + } + + /// Gets the given key's corresponding entry in the map for in-place manipulation. + /// + /// Refer to [`entry`](hb::HashMap::entry) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// let value = map.entry("foo").or_insert(0); + /// # + /// # assert_eq!(*value, 0); + /// ``` + #[inline] + pub fn entry(&mut self, key: K) -> Entry<'_, K, V, S> { + self.0.entry(key) + } + + /// Gets the given key's corresponding entry by reference in the map for in-place manipulation. + /// + /// Refer to [`entry_ref`](hb::HashMap::entry_ref) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// # let mut map: HashMap<&'static str, usize> = map; + /// + /// let value = map.entry_ref("foo").or_insert(0); + /// # + /// # assert_eq!(*value, 0); + /// ``` + #[inline] + pub fn entry_ref<'a, 'b, Q>(&'a mut self, key: &'b Q) -> EntryRef<'a, 'b, K, Q, V, S> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.entry_ref(key) + } + + /// Returns a reference to the value corresponding to the key. + /// + /// Refer to [`get`](hb::HashMap::get) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.get("foo"), Some(&0)); + /// ``` + #[inline] + pub fn get(&self, k: &Q) -> Option<&V> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get(k) + } + + /// Returns the key-value pair corresponding to the supplied key. + /// + /// Refer to [`get_key_value`](hb::HashMap::get_key_value) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.get_key_value("foo"), Some((&"foo", &0))); + /// ``` + #[inline] + pub fn get_key_value(&self, k: &Q) -> Option<(&K, &V)> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get_key_value(k) + } + + /// Returns the key-value pair corresponding to the supplied key, with a mutable reference to value. + /// + /// Refer to [`get_key_value_mut`](hb::HashMap::get_key_value_mut) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.get_key_value_mut("foo"), Some((&"foo", &mut 0))); + /// ``` + #[inline] + pub fn get_key_value_mut(&mut self, k: &Q) -> Option<(&K, &mut V)> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get_key_value_mut(k) + } + + /// Returns `true` if the map contains a value for the specified key. + /// + /// Refer to [`contains_key`](hb::HashMap::contains_key) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert!(map.contains_key("foo")); + /// ``` + #[inline] + pub fn contains_key(&self, k: &Q) -> bool + where + Q: Hash + Equivalent + ?Sized, + { + self.0.contains_key(k) + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// Refer to [`get_mut`](hb::HashMap::get_mut) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.get_mut("foo"), Some(&mut 0)); + /// ``` + #[inline] + pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get_mut(k) + } + + /// Attempts to get mutable references to `N` values in the map at once. + /// + /// Refer to [`get_many_mut`](hb::HashMap::get_many_mut) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// let result = map.get_many_mut(["foo", "bar"]); + /// + /// assert_eq!(result, [Some(&mut 0), Some(&mut 1)]); + /// ``` + #[inline] + pub fn get_many_mut(&mut self, ks: [&Q; N]) -> [Option<&'_ mut V>; N] + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get_many_mut(ks) + } + + /// Attempts to get mutable references to `N` values in the map at once, with immutable + /// references to the corresponding keys. + /// + /// Refer to [`get_many_key_value_mut`](hb::HashMap::get_many_key_value_mut) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// map.insert("bar", 1); + /// map.insert("baz", 2); + /// + /// let result = map.get_many_key_value_mut(["foo", "bar"]); + /// + /// assert_eq!(result, [Some((&"foo", &mut 0)), Some((&"bar", &mut 1))]); + /// ``` + #[inline] + pub fn get_many_key_value_mut( + &mut self, + ks: [&Q; N], + ) -> [Option<(&'_ K, &'_ mut V)>; N] + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get_many_key_value_mut(ks) + } + + /// Inserts a key-value pair into the map. + /// + /// Refer to [`insert`](hb::HashMap::insert) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.get("foo"), Some(&0)); + /// ``` + #[inline] + pub fn insert(&mut self, k: K, v: V) -> Option { + self.0.insert(k, v) + } + + /// Tries to insert a key-value pair into the map, and returns + /// a mutable reference to the value in the entry. + /// + /// Refer to [`try_insert`](hb::HashMap::try_insert) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.try_insert("foo", 0).unwrap(); + /// + /// assert!(map.try_insert("foo", 1).is_err()); + /// ``` + #[inline] + pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V, S>> { + self.0.try_insert(key, value) + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. Keeps the allocated memory for reuse. + /// + /// Refer to [`remove`](hb::HashMap::remove) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.remove("foo"), Some(0)); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn remove(&mut self, k: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + self.0.remove(k) + } + + /// Removes a key from the map, returning the stored key and value if the + /// key was previously in the map. Keeps the allocated memory for reuse. + /// + /// Refer to [`remove_entry`](hb::HashMap::remove_entry) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// map.insert("foo", 0); + /// + /// assert_eq!(map.remove_entry("foo"), Some(("foo", 0))); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn remove_entry(&mut self, k: &Q) -> Option<(K, V)> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.remove_entry(k) + } + + /// Returns the total amount of memory allocated internally by the hash + /// set, in bytes. + /// + /// Refer to [`allocation_size`](hb::HashMap::allocation_size) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashMap; + /// let mut map = HashMap::new(); + /// + /// assert_eq!(map.allocation_size(), 0); + /// + /// map.insert("foo", 0u32); + /// + /// assert!(map.allocation_size() >= size_of::<&'static str>() + size_of::()); + /// ``` + #[inline] + pub fn allocation_size(&self) -> usize { + self.0.allocation_size() + } + + /// Insert a key-value pair into the map without checking + /// if the key already exists in the map. + /// + /// Refer to [`insert_unique_unchecked`](hb::HashMap::insert_unique_unchecked) for further details. + /// + /// # Safety + /// + /// This operation is safe if a key does not exist in the map. + /// + /// However, if a key exists in the map already, the behavior is unspecified: + /// this operation may panic, loop forever, or any following operation with the map + /// may panic, loop forever or return arbitrary result. + /// + /// That said, this operation (and following operations) are guaranteed to + /// not violate memory safety. + /// + /// However this operation is still unsafe because the resulting `HashMap` + /// may be passed to unsafe code which does expect the map to behave + /// correctly, and would cause unsoundness as a result. + #[expect( + unsafe_code, + reason = "re-exporting unsafe method from Hashbrown requires unsafe code" + )] + #[inline] + pub unsafe fn insert_unique_unchecked(&mut self, key: K, value: V) -> (&K, &mut V) { + // SAFETY: safety contract is ensured by the caller. + unsafe { self.0.insert_unique_unchecked(key, value) } + } + + /// Attempts to get mutable references to `N` values in the map at once, without validating that + /// the values are unique. + /// + /// Refer to [`get_many_unchecked_mut`](hb::HashMap::get_many_unchecked_mut) for further details. + /// + /// Returns an array of length `N` with the results of each query. `None` will be used if + /// the key is missing. + /// + /// For a safe alternative see [`get_many_mut`](`HashMap::get_many_mut`). + /// + /// # Safety + /// + /// Calling this method with overlapping keys is *[undefined behavior]* even if the resulting + /// references are not used. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + #[expect( + unsafe_code, + reason = "re-exporting unsafe method from Hashbrown requires unsafe code" + )] + #[inline] + pub unsafe fn get_many_unchecked_mut( + &mut self, + keys: [&Q; N], + ) -> [Option<&'_ mut V>; N] + where + Q: Hash + Equivalent + ?Sized, + { + // SAFETY: safety contract is ensured by the caller. + unsafe { self.0.get_many_unchecked_mut(keys) } + } + + /// Attempts to get mutable references to `N` values in the map at once, with immutable + /// references to the corresponding keys, without validating that the values are unique. + /// + /// Refer to [`get_many_key_value_unchecked_mut`](hb::HashMap::get_many_key_value_unchecked_mut) for further details. + /// + /// Returns an array of length `N` with the results of each query. `None` will be returned if + /// any of the keys are missing. + /// + /// For a safe alternative see [`get_many_key_value_mut`](`HashMap::get_many_key_value_mut`). + /// + /// # Safety + /// + /// Calling this method with overlapping keys is *[undefined behavior]* even if the resulting + /// references are not used. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + #[expect( + unsafe_code, + reason = "re-exporting unsafe method from Hashbrown requires unsafe code" + )] + #[inline] + pub unsafe fn get_many_key_value_unchecked_mut( + &mut self, + keys: [&Q; N], + ) -> [Option<(&'_ K, &'_ mut V)>; N] + where + Q: Hash + Equivalent + ?Sized, + { + // SAFETY: safety contract is ensured by the caller. + unsafe { self.0.get_many_key_value_unchecked_mut(keys) } + } +} diff --git a/crates/bevy_platform/src/collections/hash_set.rs b/crates/bevy_platform/src/collections/hash_set.rs new file mode 100644 index 0000000000..7950e946db --- /dev/null +++ b/crates/bevy_platform/src/collections/hash_set.rs @@ -0,0 +1,1078 @@ +//! Provides [`HashSet`] based on [hashbrown]'s implementation. +//! Unlike [`hashbrown::HashSet`], [`HashSet`] defaults to [`FixedHasher`] +//! instead of [`RandomState`](crate::hash::RandomState). +//! This provides determinism by default with an acceptable compromise to denial +//! of service resistance in the context of a game engine. + +use core::{ + fmt::Debug, + hash::{BuildHasher, Hash}, + ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Deref, DerefMut, Sub, + SubAssign, + }, +}; + +use hashbrown::{hash_set as hb, Equivalent}; + +use crate::hash::FixedHasher; + +#[cfg(feature = "rayon")] +use rayon::prelude::{FromParallelIterator, IntoParallelIterator, ParallelExtend}; + +// Re-exports to match `std::collections::hash_set` +pub use hb::{Difference, Drain, Intersection, IntoIter, Iter, SymmetricDifference, Union}; + +// Additional items from `hashbrown` +pub use hb::{ExtractIf, OccupiedEntry, VacantEntry}; + +/// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider. +pub type Entry<'a, T, S = FixedHasher> = hb::Entry<'a, T, S>; + +/// New-type for [`HashSet`](hb::HashSet) with [`FixedHasher`] as the default hashing provider. +/// Can be trivially converted to and from a [hashbrown] [`HashSet`](hb::HashSet) using [`From`]. +/// +/// A new-type is used instead of a type alias due to critical methods like [`new`](hb::HashSet::new) +/// being incompatible with Bevy's choice of default hasher. +#[repr(transparent)] +pub struct HashSet(hb::HashSet); + +impl Clone for HashSet +where + hb::HashSet: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Self(self.0.clone()) + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.0.clone_from(&source.0); + } +} + +impl Debug for HashSet +where + hb::HashSet: Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + as Debug>::fmt(&self.0, f) + } +} + +impl Default for HashSet +where + hb::HashSet: Default, +{ + #[inline] + fn default() -> Self { + Self(Default::default()) + } +} + +impl PartialEq for HashSet +where + hb::HashSet: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for HashSet where hb::HashSet: Eq {} + +impl FromIterator for HashSet +where + hb::HashSet: FromIterator, +{ + #[inline] + fn from_iter>(iter: U) -> Self { + Self(FromIterator::from_iter(iter)) + } +} + +impl IntoIterator for HashSet +where + hb::HashSet: IntoIterator, +{ + type Item = as IntoIterator>::Item; + + type IntoIter = as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> IntoIterator for &'a HashSet +where + &'a hb::HashSet: IntoIterator, +{ + type Item = <&'a hb::HashSet as IntoIterator>::Item; + + type IntoIter = <&'a hb::HashSet as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + (&self.0).into_iter() + } +} + +impl<'a, T, S> IntoIterator for &'a mut HashSet +where + &'a mut hb::HashSet: IntoIterator, +{ + type Item = <&'a mut hb::HashSet as IntoIterator>::Item; + + type IntoIter = <&'a mut hb::HashSet as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + (&mut self.0).into_iter() + } +} + +impl Extend for HashSet +where + hb::HashSet: Extend, +{ + #[inline] + fn extend>(&mut self, iter: U) { + self.0.extend(iter); + } +} + +impl From<[T; N]> for HashSet +where + T: Eq + Hash, +{ + fn from(value: [T; N]) -> Self { + value.into_iter().collect() + } +} + +impl From> for HashSet { + #[inline] + fn from(value: crate::collections::HashMap) -> Self { + Self(hb::HashSet::from(hashbrown::HashMap::from(value))) + } +} + +impl From> for HashSet { + #[inline] + fn from(value: hb::HashSet) -> Self { + Self(value) + } +} + +impl From> for hb::HashSet { + #[inline] + fn from(value: HashSet) -> Self { + value.0 + } +} + +impl Deref for HashSet { + type Target = hb::HashSet; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HashSet { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(feature = "serialize")] +impl serde::Serialize for HashSet +where + hb::HashSet: serde::Serialize, +{ + #[inline] + fn serialize(&self, serializer: U) -> Result + where + U: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +#[cfg(feature = "serialize")] +impl<'de, T, S> serde::Deserialize<'de> for HashSet +where + hb::HashSet: serde::Deserialize<'de>, +{ + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self(serde::Deserialize::deserialize(deserializer)?)) + } +} + +#[cfg(feature = "rayon")] +impl FromParallelIterator for HashSet +where + hb::HashSet: FromParallelIterator, + U: Send, +{ + fn from_par_iter

(par_iter: P) -> Self + where + P: IntoParallelIterator, + { + Self( as FromParallelIterator>::from_par_iter(par_iter)) + } +} + +#[cfg(feature = "rayon")] +impl IntoParallelIterator for HashSet +where + hb::HashSet: IntoParallelIterator, +{ + type Item = as IntoParallelIterator>::Item; + type Iter = as IntoParallelIterator>::Iter; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "rayon")] +impl<'a, T: Sync, S> IntoParallelIterator for &'a HashSet +where + &'a hb::HashSet: IntoParallelIterator, +{ + type Item = <&'a hb::HashSet as IntoParallelIterator>::Item; + type Iter = <&'a hb::HashSet as IntoParallelIterator>::Iter; + + fn into_par_iter(self) -> Self::Iter { + (&self.0).into_par_iter() + } +} + +#[cfg(feature = "rayon")] +impl ParallelExtend for HashSet +where + hb::HashSet: ParallelExtend, + U: Send, +{ + fn par_extend(&mut self, par_iter: I) + where + I: IntoParallelIterator, + { + as ParallelExtend>::par_extend(&mut self.0, par_iter); + } +} + +impl HashSet { + /// Creates an empty [`HashSet`]. + /// + /// Refer to [`new`](hb::HashSet::new) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// // Creates a HashSet with zero capacity. + /// let map = HashSet::new(); + /// # + /// # let mut map = map; + /// # map.insert("foo"); + /// # assert_eq!(map.get("foo"), Some("foo").as_ref()); + /// ``` + #[inline] + pub const fn new() -> Self { + Self::with_hasher(FixedHasher) + } + + /// Creates an empty [`HashSet`] with the specified capacity. + /// + /// Refer to [`with_capacity`](hb::HashSet::with_capacity) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// // Creates a HashSet with capacity for at least 5 entries. + /// let map = HashSet::with_capacity(5); + /// # + /// # let mut map = map; + /// # map.insert("foo"); + /// # assert_eq!(map.get("foo"), Some("foo").as_ref()); + /// ``` + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self::with_capacity_and_hasher(capacity, FixedHasher) + } +} + +impl HashSet { + /// Returns the number of elements the set can hold without reallocating. + /// + /// Refer to [`capacity`](hb::HashSet::capacity) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let map = HashSet::with_capacity(5); + /// + /// # let map: HashSet<()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// ``` + #[inline] + pub fn capacity(&self) -> usize { + self.0.capacity() + } + + /// An iterator visiting all elements in arbitrary order. + /// The iterator element type is `&'a T`. + /// + /// Refer to [`iter`](hb::HashSet::iter) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// for value in map.iter() { + /// // "foo", "bar", "baz" + /// // Note that the above order is not guaranteed + /// } + /// # + /// # assert_eq!(map.iter().count(), 3); + /// ``` + #[inline] + pub fn iter(&self) -> Iter<'_, T> { + self.0.iter() + } + + /// Returns the number of elements in the set. + /// + /// Refer to [`len`](hb::HashSet::len) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// assert_eq!(map.len(), 0); + /// + /// map.insert("foo"); + /// + /// assert_eq!(map.len(), 1); + /// ``` + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the set contains no elements. + /// + /// Refer to [`is_empty`](hb::HashSet::is_empty) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// assert!(map.is_empty()); + /// + /// map.insert("foo"); + /// + /// assert!(!map.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Clears the set, returning all elements in an iterator. + /// + /// Refer to [`drain`](hb::HashSet::drain) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// for value in map.drain() { + /// // "foo", "bar", "baz" + /// // Note that the above order is not guaranteed + /// } + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn drain(&mut self) -> Drain<'_, T> { + self.0.drain() + } + + /// Retains only the elements specified by the predicate. + /// + /// Refer to [`retain`](hb::HashSet::retain) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// map.retain(|value| *value == "baz"); + /// + /// assert_eq!(map.len(), 1); + /// ``` + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&T) -> bool, + { + self.0.retain(f); + } + + /// Drains elements which are true under the given predicate, + /// and returns an iterator over the removed items. + /// + /// Refer to [`extract_if`](hb::HashSet::extract_if) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// let extracted = map + /// .extract_if(|value| *value == "baz") + /// .collect::>(); + /// + /// assert_eq!(map.len(), 2); + /// assert_eq!(extracted.len(), 1); + /// ``` + #[inline] + pub fn extract_if(&mut self, f: F) -> ExtractIf<'_, T, F> + where + F: FnMut(&T) -> bool, + { + self.0.extract_if(f) + } + + /// Clears the set, removing all values. + /// + /// Refer to [`clear`](hb::HashSet::clear) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// map.clear(); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Creates a new empty hash set which will use the given hasher to hash + /// keys. + /// + /// Refer to [`with_hasher`](hb::HashSet::with_hasher) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # use bevy_platform::hash::FixedHasher as SomeHasher; + /// // Creates a HashSet with the provided hasher. + /// let map = HashSet::with_hasher(SomeHasher); + /// # + /// # let mut map = map; + /// # map.insert("foo"); + /// # assert_eq!(map.get("foo"), Some("foo").as_ref()); + /// ``` + #[inline] + pub const fn with_hasher(hasher: S) -> Self { + Self(hb::HashSet::with_hasher(hasher)) + } + + /// Creates an empty [`HashSet`] with the specified capacity, using + /// `hasher` to hash the keys. + /// + /// Refer to [`with_capacity_and_hasher`](hb::HashSet::with_capacity_and_hasher) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// # use bevy_platform::hash::FixedHasher as SomeHasher; + /// // Creates a HashSet with capacity for 5 entries and the provided hasher. + /// let map = HashSet::with_capacity_and_hasher(5, SomeHasher); + /// # + /// # let mut map = map; + /// # map.insert("foo"); + /// # assert_eq!(map.get("foo"), Some("foo").as_ref()); + /// ``` + #[inline] + pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self { + Self(hb::HashSet::with_capacity_and_hasher(capacity, hasher)) + } + + /// Returns a reference to the set's [`BuildHasher`]. + /// + /// Refer to [`hasher`](hb::HashSet::hasher) for further details. + #[inline] + pub fn hasher(&self) -> &S { + self.0.hasher() + } + + /// Takes the inner [`HashSet`](hb::HashSet) out of this wrapper. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let map: HashSet<&'static str> = HashSet::new(); + /// let map: hashbrown::HashSet<&'static str, _> = map.into_inner(); + /// ``` + #[inline] + pub fn into_inner(self) -> hb::HashSet { + self.0 + } +} + +impl HashSet +where + T: Eq + Hash, + S: BuildHasher, +{ + /// Reserves capacity for at least `additional` more elements to be inserted + /// in the [`HashSet`]. The collection may reserve more space to avoid + /// frequent reallocations. + /// + /// Refer to [`reserve`](hb::HashSet::reserve) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::with_capacity(5); + /// + /// # let mut map: HashSet<()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// + /// map.reserve(10); + /// + /// assert!(map.capacity() - map.len() >= 10); + /// ``` + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.0.reserve(additional); + } + + /// Tries to reserve capacity for at least `additional` more elements to be inserted + /// in the given `HashSet`. The collection may reserve more space to avoid + /// frequent reallocations. + /// + /// Refer to [`try_reserve`](hb::HashSet::try_reserve) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::with_capacity(5); + /// + /// # let mut map: HashSet<()> = map; + /// # + /// assert!(map.capacity() >= 5); + /// + /// map.try_reserve(10).expect("Out of Memory!"); + /// + /// assert!(map.capacity() - map.len() >= 10); + /// ``` + #[inline] + pub fn try_reserve(&mut self, additional: usize) -> Result<(), hashbrown::TryReserveError> { + self.0.try_reserve(additional) + } + + /// Shrinks the capacity of the set as much as possible. It will drop + /// down as much as possible while maintaining the internal rules + /// and possibly leaving some space in accordance with the resize policy. + /// + /// Refer to [`shrink_to_fit`](hb::HashSet::shrink_to_fit) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::with_capacity(5); + /// + /// map.insert("foo"); + /// map.insert("bar"); + /// map.insert("baz"); + /// + /// assert!(map.capacity() >= 5); + /// + /// map.shrink_to_fit(); + /// + /// assert_eq!(map.capacity(), 3); + /// ``` + #[inline] + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit(); + } + + /// Shrinks the capacity of the set with a lower limit. It will drop + /// down no lower than the supplied limit while maintaining the internal rules + /// and possibly leaving some space in accordance with the resize policy. + /// + /// Refer to [`shrink_to`](hb::HashSet::shrink_to) for further details. + #[inline] + pub fn shrink_to(&mut self, min_capacity: usize) { + self.0.shrink_to(min_capacity); + } + + /// Visits the values representing the difference, + /// i.e., the values that are in `self` but not in `other`. + /// + /// Refer to [`difference`](hb::HashSet::difference) for further details. + #[inline] + pub fn difference<'a>(&'a self, other: &'a Self) -> Difference<'a, T, S> { + self.0.difference(other) + } + + /// Visits the values representing the symmetric difference, + /// i.e., the values that are in `self` or in `other` but not in both. + /// + /// Refer to [`symmetric_difference`](hb::HashSet::symmetric_difference) for further details. + #[inline] + pub fn symmetric_difference<'a>(&'a self, other: &'a Self) -> SymmetricDifference<'a, T, S> { + self.0.symmetric_difference(other) + } + + /// Visits the values representing the intersection, + /// i.e., the values that are both in `self` and `other`. + /// + /// Refer to [`intersection`](hb::HashSet::intersection) for further details. + #[inline] + pub fn intersection<'a>(&'a self, other: &'a Self) -> Intersection<'a, T, S> { + self.0.intersection(other) + } + + /// Visits the values representing the union, + /// i.e., all the values in `self` or `other`, without duplicates. + /// + /// Refer to [`union`](hb::HashSet::union) for further details. + #[inline] + pub fn union<'a>(&'a self, other: &'a Self) -> Union<'a, T, S> { + self.0.union(other) + } + + /// Returns `true` if the set contains a value. + /// + /// Refer to [`contains`](hb::HashSet::contains) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert!(map.contains("foo")); + /// ``` + #[inline] + pub fn contains(&self, value: &Q) -> bool + where + Q: Hash + Equivalent + ?Sized, + { + self.0.contains(value) + } + + /// Returns a reference to the value in the set, if any, that is equal to the given value. + /// + /// Refer to [`get`](hb::HashSet::get) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert_eq!(map.get("foo"), Some(&"foo")); + /// ``` + #[inline] + pub fn get(&self, value: &Q) -> Option<&T> + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get(value) + } + + /// Inserts the given `value` into the set if it is not present, then + /// returns a reference to the value in the set. + /// + /// Refer to [`get_or_insert`](hb::HashSet::get_or_insert) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// assert_eq!(map.get_or_insert("foo"), &"foo"); + /// ``` + #[inline] + pub fn get_or_insert(&mut self, value: T) -> &T { + self.0.get_or_insert(value) + } + + /// Inserts a value computed from `f` into the set if the given `value` is + /// not present, then returns a reference to the value in the set. + /// + /// Refer to [`get_or_insert_with`](hb::HashSet::get_or_insert_with) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// assert_eq!(map.get_or_insert_with(&"foo", |_| "foo"), &"foo"); + /// ``` + #[inline] + pub fn get_or_insert_with(&mut self, value: &Q, f: F) -> &T + where + Q: Hash + Equivalent + ?Sized, + F: FnOnce(&Q) -> T, + { + self.0.get_or_insert_with(value, f) + } + + /// Gets the given value's corresponding entry in the set for in-place manipulation. + /// + /// Refer to [`entry`](hb::HashSet::entry) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// let value = map.entry("foo").or_insert(); + /// # + /// # assert_eq!(value, ()); + /// ``` + #[inline] + pub fn entry(&mut self, value: T) -> Entry<'_, T, S> { + self.0.entry(value) + } + + /// Returns `true` if `self` has no elements in common with `other`. + /// This is equivalent to checking for an empty intersection. + /// + /// Refer to [`is_disjoint`](hb::HashSet::is_disjoint) for further details. + #[inline] + pub fn is_disjoint(&self, other: &Self) -> bool { + self.0.is_disjoint(other) + } + + /// Returns `true` if the set is a subset of another, + /// i.e., `other` contains at least all the values in `self`. + /// + /// Refer to [`is_subset`](hb::HashSet::is_subset) for further details. + #[inline] + pub fn is_subset(&self, other: &Self) -> bool { + self.0.is_subset(other) + } + + /// Returns `true` if the set is a superset of another, + /// i.e., `self` contains at least all the values in `other`. + /// + /// Refer to [`is_superset`](hb::HashSet::is_superset) for further details. + #[inline] + pub fn is_superset(&self, other: &Self) -> bool { + self.0.is_superset(other) + } + + /// Adds a value to the set. + /// + /// Refer to [`insert`](hb::HashSet::insert) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert!(map.contains("foo")); + /// ``` + #[inline] + pub fn insert(&mut self, value: T) -> bool { + self.0.insert(value) + } + + /// Adds a value to the set, replacing the existing value, if any, that is equal to the given + /// one. Returns the replaced value. + /// + /// Refer to [`replace`](hb::HashSet::replace) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert_eq!(map.replace("foo"), Some("foo")); + /// ``` + #[inline] + pub fn replace(&mut self, value: T) -> Option { + self.0.replace(value) + } + + /// Removes a value from the set. Returns whether the value was + /// present in the set. + /// + /// Refer to [`remove`](hb::HashSet::remove) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert!(map.remove("foo")); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn remove(&mut self, value: &Q) -> bool + where + Q: Hash + Equivalent + ?Sized, + { + self.0.remove(value) + } + + /// Removes and returns the value in the set, if any, that is equal to the given one. + /// + /// Refer to [`take`](hb::HashSet::take) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// map.insert("foo"); + /// + /// assert_eq!(map.take("foo"), Some("foo")); + /// + /// assert!(map.is_empty()); + /// ``` + #[inline] + pub fn take(&mut self, value: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + self.0.take(value) + } + + /// Returns the total amount of memory allocated internally by the hash + /// set, in bytes. + /// + /// Refer to [`allocation_size`](hb::HashSet::allocation_size) for further details. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_platform::collections::HashSet; + /// let mut map = HashSet::new(); + /// + /// assert_eq!(map.allocation_size(), 0); + /// + /// map.insert("foo"); + /// + /// assert!(map.allocation_size() >= size_of::<&'static str>()); + /// ``` + #[inline] + pub fn allocation_size(&self) -> usize { + self.0.allocation_size() + } + + /// Insert a value the set without checking if the value already exists in the set. + /// + /// Refer to [`insert_unique_unchecked`](hb::HashSet::insert_unique_unchecked) for further details. + /// + /// # Safety + /// + /// This operation is safe if a value does not exist in the set. + /// + /// However, if a value exists in the set already, the behavior is unspecified: + /// this operation may panic, loop forever, or any following operation with the set + /// may panic, loop forever or return arbitrary result. + /// + /// That said, this operation (and following operations) are guaranteed to + /// not violate memory safety. + /// + /// However this operation is still unsafe because the resulting `HashSet` + /// may be passed to unsafe code which does expect the set to behave + /// correctly, and would cause unsoundness as a result. + #[expect( + unsafe_code, + reason = "re-exporting unsafe method from Hashbrown requires unsafe code" + )] + #[inline] + pub unsafe fn insert_unique_unchecked(&mut self, value: T) -> &T { + // SAFETY: safety contract is ensured by the caller. + unsafe { self.0.insert_unique_unchecked(value) } + } +} + +impl BitOr<&HashSet> for &HashSet +where + for<'a> &'a hb::HashSet: BitOr<&'a hb::HashSet, Output = hb::HashSet>, +{ + type Output = HashSet; + + /// Returns the union of `self` and `rhs` as a new `HashSet`. + #[inline] + fn bitor(self, rhs: &HashSet) -> HashSet { + HashSet(self.0.bitor(&rhs.0)) + } +} + +impl BitAnd<&HashSet> for &HashSet +where + for<'a> &'a hb::HashSet: BitAnd<&'a hb::HashSet, Output = hb::HashSet>, +{ + type Output = HashSet; + + /// Returns the intersection of `self` and `rhs` as a new `HashSet`. + #[inline] + fn bitand(self, rhs: &HashSet) -> HashSet { + HashSet(self.0.bitand(&rhs.0)) + } +} + +impl BitXor<&HashSet> for &HashSet +where + for<'a> &'a hb::HashSet: BitXor<&'a hb::HashSet, Output = hb::HashSet>, +{ + type Output = HashSet; + + /// Returns the symmetric difference of `self` and `rhs` as a new `HashSet`. + #[inline] + fn bitxor(self, rhs: &HashSet) -> HashSet { + HashSet(self.0.bitxor(&rhs.0)) + } +} + +impl Sub<&HashSet> for &HashSet +where + for<'a> &'a hb::HashSet: Sub<&'a hb::HashSet, Output = hb::HashSet>, +{ + type Output = HashSet; + + /// Returns the difference of `self` and `rhs` as a new `HashSet`. + #[inline] + fn sub(self, rhs: &HashSet) -> HashSet { + HashSet(self.0.sub(&rhs.0)) + } +} + +impl BitOrAssign<&HashSet> for HashSet +where + hb::HashSet: for<'a> BitOrAssign<&'a hb::HashSet>, +{ + /// Modifies this set to contain the union of `self` and `rhs`. + #[inline] + fn bitor_assign(&mut self, rhs: &HashSet) { + self.0.bitor_assign(&rhs.0); + } +} + +impl BitAndAssign<&HashSet> for HashSet +where + hb::HashSet: for<'a> BitAndAssign<&'a hb::HashSet>, +{ + /// Modifies this set to contain the intersection of `self` and `rhs`. + #[inline] + fn bitand_assign(&mut self, rhs: &HashSet) { + self.0.bitand_assign(&rhs.0); + } +} + +impl BitXorAssign<&HashSet> for HashSet +where + hb::HashSet: for<'a> BitXorAssign<&'a hb::HashSet>, +{ + /// Modifies this set to contain the symmetric difference of `self` and `rhs`. + #[inline] + fn bitxor_assign(&mut self, rhs: &HashSet) { + self.0.bitxor_assign(&rhs.0); + } +} + +impl SubAssign<&HashSet> for HashSet +where + hb::HashSet: for<'a> SubAssign<&'a hb::HashSet>, +{ + /// Modifies this set to contain the difference of `self` and `rhs`. + #[inline] + fn sub_assign(&mut self, rhs: &HashSet) { + self.0.sub_assign(&rhs.0); + } +} diff --git a/crates/bevy_platform/src/collections/hash_table.rs b/crates/bevy_platform/src/collections/hash_table.rs new file mode 100644 index 0000000000..5d6a265679 --- /dev/null +++ b/crates/bevy_platform/src/collections/hash_table.rs @@ -0,0 +1,6 @@ +//! Provides [`HashTable`] + +pub use hashbrown::hash_table::{ + AbsentEntry, Drain, Entry, ExtractIf, HashTable, IntoIter, Iter, IterHash, IterHashMut, + IterMut, OccupiedEntry, VacantEntry, +}; diff --git a/crates/bevy_platform/src/collections/mod.rs b/crates/bevy_platform/src/collections/mod.rs new file mode 100644 index 0000000000..3622165b65 --- /dev/null +++ b/crates/bevy_platform/src/collections/mod.rs @@ -0,0 +1,12 @@ +//! Provides [`HashMap`] and [`HashSet`] from [`hashbrown`] with some customized defaults. +//! +//! Also provides the [`HashTable`] type, which is specific to [`hashbrown`]. + +pub use hash_map::HashMap; +pub use hash_set::HashSet; +pub use hash_table::HashTable; +pub use hashbrown::Equivalent; + +pub mod hash_map; +pub mod hash_set; +pub mod hash_table; diff --git a/crates/bevy_platform_support/src/hash.rs b/crates/bevy_platform/src/hash.rs similarity index 100% rename from crates/bevy_platform_support/src/hash.rs rename to crates/bevy_platform/src/hash.rs diff --git a/crates/bevy_platform_support/src/lib.rs b/crates/bevy_platform/src/lib.rs similarity index 78% rename from crates/bevy_platform_support/src/lib.rs rename to crates/bevy_platform/src/lib.rs index eada254595..d5871defb4 100644 --- a/crates/bevy_platform_support/src/lib.rs +++ b/crates/bevy_platform/src/lib.rs @@ -9,19 +9,23 @@ //! //! [Bevy]: https://bevyengine.org/ -#[cfg(feature = "std")] -extern crate std; +cfg::std! { + extern crate std; +} -#[cfg(feature = "alloc")] -extern crate alloc; +cfg::alloc! { + extern crate alloc; + pub mod collections; +} + +pub mod cell; +pub mod cfg; pub mod hash; pub mod sync; +pub mod thread; pub mod time; -#[cfg(feature = "alloc")] -pub mod collections; - /// Frequently used items which would typically be included in most contexts. /// /// When adding `no_std` support to a crate for the first time, often there's a substantial refactor @@ -32,10 +36,11 @@ pub mod collections; /// This prelude aims to ease the transition by re-exporting items from `alloc` which would /// otherwise be included in the `std` implicit prelude. pub mod prelude { - #[cfg(feature = "alloc")] - pub use alloc::{ - borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, - }; + crate::cfg::alloc! { + pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, + }; + } // Items from `std::prelude` that are missing in this module: // * dbg diff --git a/crates/bevy_platform_support/src/sync/atomic.rs b/crates/bevy_platform/src/sync/atomic.rs similarity index 100% rename from crates/bevy_platform_support/src/sync/atomic.rs rename to crates/bevy_platform/src/sync/atomic.rs diff --git a/crates/bevy_platform_support/src/sync/barrier.rs b/crates/bevy_platform/src/sync/barrier.rs similarity index 94% rename from crates/bevy_platform_support/src/sync/barrier.rs rename to crates/bevy_platform/src/sync/barrier.rs index 6c179d81d6..2968a78b01 100644 --- a/crates/bevy_platform_support/src/sync/barrier.rs +++ b/crates/bevy_platform/src/sync/barrier.rs @@ -1,12 +1,12 @@ //! Provides `Barrier` and `BarrierWaitResult` -pub use barrier::{Barrier, BarrierWaitResult}; +pub use implementation::{Barrier, BarrierWaitResult}; #[cfg(feature = "std")] -use std::sync as barrier; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod barrier { +mod implementation { use core::fmt; /// Fallback implementation of `Barrier` from the standard library. diff --git a/crates/bevy_platform_support/src/sync/lazy_lock.rs b/crates/bevy_platform/src/sync/lazy_lock.rs similarity index 57% rename from crates/bevy_platform_support/src/sync/lazy_lock.rs rename to crates/bevy_platform/src/sync/lazy_lock.rs index 8a13c1bef2..c756daeb94 100644 --- a/crates/bevy_platform_support/src/sync/lazy_lock.rs +++ b/crates/bevy_platform/src/sync/lazy_lock.rs @@ -1,11 +1,11 @@ //! Provides `LazyLock` -pub use lazy_lock::LazyLock; +pub use implementation::LazyLock; #[cfg(feature = "std")] -use std::sync as lazy_lock; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod lazy_lock { +mod implementation { pub use spin::Lazy as LazyLock; } diff --git a/crates/bevy_platform_support/src/sync/mod.rs b/crates/bevy_platform/src/sync/mod.rs similarity index 73% rename from crates/bevy_platform_support/src/sync/mod.rs rename to crates/bevy_platform/src/sync/mod.rs index 8fb7a2fbff..79ceff7ee8 100644 --- a/crates/bevy_platform_support/src/sync/mod.rs +++ b/crates/bevy_platform/src/sync/mod.rs @@ -14,8 +14,17 @@ pub use once::{Once, OnceLock, OnceState}; pub use poison::{LockResult, PoisonError, TryLockError, TryLockResult}; pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -#[cfg(feature = "alloc")] -pub use arc::{Arc, Weak}; +crate::cfg::alloc! { + pub use arc::{Arc, Weak}; + + crate::cfg::arc! { + if { + use alloc::sync as arc; + } else { + use portable_atomic_util as arc; + } + } +} pub mod atomic; @@ -25,9 +34,3 @@ mod mutex; mod once; mod poison; mod rwlock; - -#[cfg(all(feature = "alloc", not(target_has_atomic = "ptr")))] -use portable_atomic_util as arc; - -#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] -use alloc::sync as arc; diff --git a/crates/bevy_platform_support/src/sync/mutex.rs b/crates/bevy_platform/src/sync/mutex.rs similarity index 95% rename from crates/bevy_platform_support/src/sync/mutex.rs rename to crates/bevy_platform/src/sync/mutex.rs index a059d670e9..7ff363f574 100644 --- a/crates/bevy_platform_support/src/sync/mutex.rs +++ b/crates/bevy_platform/src/sync/mutex.rs @@ -1,12 +1,12 @@ //! Provides `Mutex` and `MutexGuard` -pub use mutex::{Mutex, MutexGuard}; +pub use implementation::{Mutex, MutexGuard}; #[cfg(feature = "std")] -use std::sync as mutex; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod mutex { +mod implementation { use crate::sync::{LockResult, TryLockError, TryLockResult}; use core::fmt; @@ -81,7 +81,7 @@ mod mutex { } } - impl Default for Mutex { + impl Default for Mutex { fn default() -> Mutex { Mutex::new(Default::default()) } diff --git a/crates/bevy_platform_support/src/sync/once.rs b/crates/bevy_platform/src/sync/once.rs similarity index 97% rename from crates/bevy_platform_support/src/sync/once.rs rename to crates/bevy_platform/src/sync/once.rs index 2ae733f387..f4ac34b905 100644 --- a/crates/bevy_platform_support/src/sync/once.rs +++ b/crates/bevy_platform/src/sync/once.rs @@ -1,12 +1,12 @@ //! Provides `Once`, `OnceState`, `OnceLock` -pub use once::{Once, OnceLock, OnceState}; +pub use implementation::{Once, OnceLock, OnceState}; #[cfg(feature = "std")] -use std::sync as once; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod once { +mod implementation { use core::{ fmt, panic::{RefUnwindSafe, UnwindSafe}, @@ -145,6 +145,7 @@ mod once { /// Creates a new `Once` value. /// /// See the standard library for further details. + #[expect(clippy::new_without_default, reason = "matching std::sync::Once")] pub const fn new() -> Self { Self { inner: OnceLock::new(), diff --git a/crates/bevy_platform_support/src/sync/poison.rs b/crates/bevy_platform/src/sync/poison.rs similarity index 96% rename from crates/bevy_platform_support/src/sync/poison.rs rename to crates/bevy_platform/src/sync/poison.rs index 0aa8e168c2..79eafc4250 100644 --- a/crates/bevy_platform_support/src/sync/poison.rs +++ b/crates/bevy_platform/src/sync/poison.rs @@ -1,12 +1,12 @@ //! Provides `LockResult`, `PoisonError`, `TryLockError`, `TryLockResult` -pub use poison::{LockResult, PoisonError, TryLockError, TryLockResult}; +pub use implementation::{LockResult, PoisonError, TryLockError, TryLockResult}; #[cfg(feature = "std")] -use std::sync as poison; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod poison { +mod implementation { use core::{error::Error, fmt}; /// Fallback implementation of `PoisonError` from the standard library. diff --git a/crates/bevy_platform_support/src/sync/rwlock.rs b/crates/bevy_platform/src/sync/rwlock.rs similarity index 95% rename from crates/bevy_platform_support/src/sync/rwlock.rs rename to crates/bevy_platform/src/sync/rwlock.rs index 627da73f32..f1f529baaf 100644 --- a/crates/bevy_platform_support/src/sync/rwlock.rs +++ b/crates/bevy_platform/src/sync/rwlock.rs @@ -1,12 +1,12 @@ -//! TODO: Implement `RwLock`, `RwLockReadGuard`, `RwLockWriteGuard` +//! Provides `RwLock`, `RwLockReadGuard`, `RwLockWriteGuard` -pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +pub use implementation::{RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(feature = "std")] -use std::sync as rwlock; +use std::sync as implementation; #[cfg(not(feature = "std"))] -mod rwlock { +mod implementation { use crate::sync::{LockResult, TryLockError, TryLockResult}; use core::fmt; diff --git a/crates/bevy_platform/src/thread.rs b/crates/bevy_platform/src/thread.rs new file mode 100644 index 0000000000..6e4650382e --- /dev/null +++ b/crates/bevy_platform/src/thread.rs @@ -0,0 +1,31 @@ +//! Provides `sleep` for all platforms. + +pub use thread::sleep; + +crate::cfg::switch! { + // TODO: use browser timeouts based on ScheduleRunnerPlugin::build + // crate::cfg::web => { ... } + crate::cfg::std => { + use std::thread; + } + _ => { + mod fallback { + use core::{hint::spin_loop, time::Duration}; + + use crate::time::Instant; + + /// Puts the current thread to sleep for at least the specified amount of time. + /// + /// As this is a `no_std` fallback implementation, this will spin the current thread. + pub fn sleep(dur: Duration) { + let start = Instant::now(); + + while start.elapsed() < dur { + spin_loop() + } + } + } + + use fallback as thread; + } +} diff --git a/crates/bevy_platform_support/src/time/fallback.rs b/crates/bevy_platform/src/time/fallback.rs similarity index 79% rename from crates/bevy_platform_support/src/time/fallback.rs rename to crates/bevy_platform/src/time/fallback.rs index a0a9354902..c649f6a49d 100644 --- a/crates/bevy_platform_support/src/time/fallback.rs +++ b/crates/bevy_platform/src/time/fallback.rs @@ -36,7 +36,7 @@ impl Instant { let getter = ELAPSED_GETTER.load(Ordering::Acquire); // SAFETY: Function pointer is always valid - let getter = unsafe { core::mem::transmute::<_, fn() -> Duration>(getter) }; + let getter = unsafe { core::mem::transmute::<*mut (), fn() -> Duration>(getter) }; Self((getter)()) } @@ -80,7 +80,7 @@ impl Instant { /// Returns the amount of time elapsed since this instant. #[must_use] pub fn elapsed(&self) -> Duration { - self.saturating_duration_since(Instant::now()) + Instant::now().saturating_duration_since(*self) } /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as @@ -149,28 +149,32 @@ impl fmt::Debug for Instant { } fn unset_getter() -> Duration { - let _nanos: u64; - - #[cfg(target_arch = "x86")] - unsafe { - _nanos = core::arch::x86::_rdtsc(); + crate::cfg::switch! { + #[cfg(target_arch = "x86")] => { + // SAFETY: standard technique for getting a nanosecond counter on x86 + let nanos = unsafe { + core::arch::x86::_rdtsc() + }; + return Duration::from_nanos(nanos); + } + #[cfg(target_arch = "x86_64")] => { + // SAFETY: standard technique for getting a nanosecond counter on x86_64 + let nanos = unsafe { + core::arch::x86_64::_rdtsc() + }; + return Duration::from_nanos(nanos); + } + #[cfg(target_arch = "aarch64")] => { + // SAFETY: standard technique for getting a nanosecond counter of aarch64 + let nanos = unsafe { + let mut ticks: u64; + core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks); + ticks + }; + return Duration::from_nanos(nanos); + } + _ => { + panic!("An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`") + } } - - #[cfg(target_arch = "x86_64")] - unsafe { - _nanos = core::arch::x86_64::_rdtsc(); - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let mut ticks: u64; - core::arch::asm!("mrs {}, cntvct_el0", out(reg) ticks); - _nanos = ticks; - } - - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] - panic!("An elapsed time getter has not been provided to `Instant`. Please use `Instant::set_elapsed(...)` before calling `Instant::now()`"); - - #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] - return Duration::from_nanos(_nanos); } diff --git a/crates/bevy_platform_support/src/time/mod.rs b/crates/bevy_platform/src/time/mod.rs similarity index 57% rename from crates/bevy_platform_support/src/time/mod.rs rename to crates/bevy_platform/src/time/mod.rs index 260d8e4aea..10b9d3d131 100644 --- a/crates/bevy_platform_support/src/time/mod.rs +++ b/crates/bevy_platform/src/time/mod.rs @@ -2,12 +2,14 @@ pub use time::Instant; -cfg_if::cfg_if! { - if #[cfg(all(target_arch = "wasm32", feature = "web"))] { +crate::cfg::switch! { + crate::cfg::web => { use web_time as time; - } else if #[cfg(feature = "std")] { + } + crate::cfg::std => { use std::time; - } else { + } + _ => { mod fallback; use fallback as time; diff --git a/crates/bevy_platform_support/src/collections.rs b/crates/bevy_platform_support/src/collections.rs deleted file mode 100644 index d3f11431e4..0000000000 --- a/crates/bevy_platform_support/src/collections.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Provides [`HashMap`] and [`HashSet`] from [`hashbrown`] with some customized defaults. -//! -//! Also provides the [`HashTable`] type, which is specific to [`hashbrown`]. -//! -//! Note that due to the implementation details of [`hashbrown`], [`HashMap::new`] is only implemented for `HashMap`. -//! Whereas, Bevy exports `HashMap` as its default [`HashMap`] type, meaning [`HashMap::new`] will typically fail. -//! To bypass this issue, use [`HashMap::default`] instead. - -pub use hash_map::HashMap; -pub use hash_set::HashSet; -pub use hash_table::HashTable; -pub use hashbrown::Equivalent; - -pub mod hash_map { - //! Provides [`HashMap`] - - use crate::hash::FixedHasher; - use hashbrown::hash_map as hb; - - // Re-exports to match `std::collections::hash_map` - pub use { - crate::hash::{DefaultHasher, RandomState}, - hb::{ - Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, OccupiedEntry, VacantEntry, - Values, ValuesMut, - }, - }; - - // Additional items from `hashbrown` - pub use hb::{ - EntryRef, ExtractIf, OccupiedError, RawEntryBuilder, RawEntryBuilderMut, RawEntryMut, - RawOccupiedEntryMut, - }; - - /// Shortcut for [`HashMap`](hb::HashMap) with [`FixedHasher`] as the default hashing provider. - pub type HashMap = hb::HashMap; - - /// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider. - pub type Entry<'a, K, V, S = FixedHasher> = hb::Entry<'a, K, V, S>; -} - -pub mod hash_set { - //! Provides [`HashSet`] - - use crate::hash::FixedHasher; - use hashbrown::hash_set as hb; - - // Re-exports to match `std::collections::hash_set` - pub use hb::{Difference, Drain, Intersection, IntoIter, Iter, SymmetricDifference, Union}; - - // Additional items from `hashbrown` - pub use hb::{ExtractIf, OccupiedEntry, VacantEntry}; - - /// Shortcut for [`HashSet`](hb::HashSet) with [`FixedHasher`] as the default hashing provider. - pub type HashSet = hb::HashSet; - - /// Shortcut for [`Entry`](hb::Entry) with [`FixedHasher`] as the default hashing provider. - pub type Entry<'a, T, S = FixedHasher> = hb::Entry<'a, T, S>; -} - -pub mod hash_table { - //! Provides [`HashTable`] - - pub use hashbrown::hash_table::{ - AbsentEntry, Drain, Entry, ExtractIf, HashTable, IntoIter, Iter, IterHash, IterHashMut, - IterMut, OccupiedEntry, VacantEntry, - }; -} diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 0f54d8cccf..8fff0a331f 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -33,6 +33,9 @@ debug_stack = ["std"] ## Adds reflection support to `glam` types. glam = ["dep:glam"] +## Adds reflection support to `hashbrown` types. +hashbrown = ["dep:hashbrown"] + ## Adds reflection support to `petgraph` types. petgraph = ["dep:petgraph", "std"] @@ -51,42 +54,37 @@ wgpu-types = ["dep:wgpu-types"] ## on `no_std` targets, but provides access to certain additional features on ## supported platforms. std = [ - "bevy_utils/std", "erased-serde/std", "downcast-rs/std", "serde/std", "glam?/std", "smol_str?/std", "uuid?/std", - "bevy_platform_support/std", + "bevy_platform/std", "wgpu-types?/std", ] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. -critical-section = [ - "bevy_platform_support/critical-section", - "bevy_utils/critical-section", -] +critical-section = ["bevy_platform/critical-section"] ## Enables use of browser APIs. ## Note this is currently only applicable on `wasm32` architectures. -web = ["bevy_platform_support/web", "uuid?/js"] +web = ["bevy_platform/web", "uuid?/js"] [dependencies] # bevy bevy_reflect_derive = { path = "derive", version = "0.16.0-dev" } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ - "alloc", -] } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "alloc", "serialize", ] } # used by bevy-utils, but it also needs reflect impls foldhash = { version = "0.1.3", default-features = false } +hashbrown = { version = "0.15.1", optional = true, default-features = false } # other erased-serde = { version = "0.4", default-features = false, features = [ @@ -99,7 +97,7 @@ derive_more = { version = "1", default-features = false, features = ["from"] } serde = { version = "1", default-features = false, features = ["alloc"] } assert_type_match = "0.1.1" smallvec = { version = "1.11", default-features = false, optional = true } -glam = { version = "0.29", default-features = false, features = [ +glam = { version = "0.29.3", default-features = false, features = [ "serde", ], optional = true } petgraph = { version = "0.7", features = ["serde-1"], optional = true } @@ -118,8 +116,8 @@ wgpu-types = { version = "24", features = [ [dev-dependencies] ron = "0.8.0" rmp-serde = "1.1" -bincode = "1.3" -serde_json = "1.0" +bincode = { version = "2.0", features = ["serde"] } +serde_json = "1.0.140" serde = { version = "1", features = ["derive"] } static_assertions = "1.1.0" diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index 2cec1db0b4..8b2ae0351f 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -401,9 +401,11 @@ impl ContainerAttributes { // Override `lit` if this is a `FromReflect` derive. // This typically means a user is opting out of the default implementation // from the `Reflect` derive and using the `FromReflect` derive directly instead. - (trait_ == ReflectTraitToImpl::FromReflect) - .then(|| LitBool::new(true, Span::call_site())) - .unwrap_or_else(|| lit.clone()) + if trait_ == ReflectTraitToImpl::FromReflect { + LitBool::new(true, Span::call_site()) + } else { + lit.clone() + } })?; if let Some(existing) = &self.from_reflect_attrs.auto_derive { @@ -434,9 +436,11 @@ impl ContainerAttributes { // Override `lit` if this is a `FromReflect` derive. // This typically means a user is opting out of the default implementation // from the `Reflect` derive and using the `FromReflect` derive directly instead. - (trait_ == ReflectTraitToImpl::TypePath) - .then(|| LitBool::new(true, Span::call_site())) - .unwrap_or_else(|| lit.clone()) + if trait_ == ReflectTraitToImpl::TypePath { + LitBool::new(true, Span::call_site()) + } else { + lit.clone() + } })?; if let Some(existing) = &self.type_path_attrs.auto_derive { diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index eeb6c6d24a..f825cb2905 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -1100,7 +1100,7 @@ pub(crate) enum ReflectTypePath<'a> { reason = "Not currently used but may be useful in the future due to its generality." )] Anonymous { - qualified_type: Type, + qualified_type: Box, long_type_path: StringExpr, short_type_path: StringExpr, }, diff --git a/crates/bevy_reflect/derive/src/string_expr.rs b/crates/bevy_reflect/derive/src/string_expr.rs index cc48a90b91..dc878f39a9 100644 --- a/crates/bevy_reflect/derive/src/string_expr.rs +++ b/crates/bevy_reflect/derive/src/string_expr.rs @@ -80,7 +80,7 @@ impl StringExpr { let owned = self.into_owned(); let borrowed = other.into_borrowed(); Self::Owned(quote! { - #owned + #borrowed + ::core::ops::Add::<&str>::add(#owned, #borrowed) }) } } diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 9ad906cfce..55f62b34c8 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -68,12 +68,6 @@ pub trait Array: PartialReflect { /// Drain the elements of this array to get a vector of owned values. fn drain(self: Box) -> Vec>; - /// Clones the list, producing a [`DynamicArray`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_array` instead")] - fn clone_dynamic(&self) -> DynamicArray { - self.to_dynamic_array() - } - /// Creates a new [`DynamicArray`] from this array. fn to_dynamic_array(&self) -> DynamicArray { DynamicArray { @@ -192,8 +186,7 @@ impl DynamicArray { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Array(_)), - "expected TypeInfo::Array but received: {:?}", - represented_type + "expected TypeInfo::Array but received: {represented_type:?}" ); } diff --git a/crates/bevy_reflect/src/attributes.rs b/crates/bevy_reflect/src/attributes.rs index a6edefab25..728102c4b0 100644 --- a/crates/bevy_reflect/src/attributes.rs +++ b/crates/bevy_reflect/src/attributes.rs @@ -213,7 +213,7 @@ mod tests { fn should_debug_custom_attributes() { let attributes = CustomAttributes::default().with_attribute("My awesome custom attribute!"); - let debug = format!("{:?}", attributes); + let debug = format!("{attributes:?}"); assert_eq!(r#"{"My awesome custom attribute!"}"#, debug); @@ -224,7 +224,7 @@ mod tests { let attributes = CustomAttributes::default().with_attribute(Foo { value: 42 }); - let debug = format!("{:?}", attributes); + let debug = format!("{attributes:?}"); assert_eq!( r#"{bevy_reflect::attributes::tests::Foo { value: 42 }}"#, diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index a968b311f2..3f0b275519 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -114,8 +114,7 @@ impl DynamicEnum { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Enum(_)), - "expected TypeInfo::Enum but received: {:?}", - represented_type + "expected TypeInfo::Enum but received: {represented_type:?}", ); } @@ -140,6 +139,22 @@ impl DynamicEnum { self.variant = variant.into(); } + /// Get a reference to the [`DynamicVariant`] contained in `self`. + pub fn variant(&self) -> &DynamicVariant { + &self.variant + } + + /// Get a mutable reference to the [`DynamicVariant`] contained in `self`. + /// + /// Using the mut reference to switch to a different variant will ___not___ update the + /// internal tracking of the variant name and index. + /// + /// If you want to switch variants, prefer one of the setters: + /// [`DynamicEnum::set_variant`] or [`DynamicEnum::set_variant_with_index`]. + pub fn variant_mut(&mut self) -> &mut DynamicVariant { + &mut self.variant + } + /// Create a [`DynamicEnum`] from an existing one. /// /// This is functionally the same as [`DynamicEnum::from_ref`] except it takes an owned value. @@ -264,15 +279,6 @@ impl Enum for DynamicEnum { DynamicVariant::Struct(..) => VariantType::Struct, } } - - fn clone_dynamic(&self) -> DynamicEnum { - Self { - represented_type: self.represented_type, - variant_index: self.variant_index, - variant_name: self.variant_name.clone(), - variant: self.variant.clone(), - } - } } impl PartialReflect for DynamicEnum { diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index 4df7aea527..126c407f23 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -5,8 +5,8 @@ use crate::{ DynamicEnum, Generics, PartialReflect, Type, TypePath, VariantInfo, VariantType, }; use alloc::{boxed::Box, format, string::String}; -use bevy_platform_support::collections::HashMap; -use bevy_platform_support::sync::Arc; +use bevy_platform::collections::HashMap; +use bevy_platform::sync::Arc; use core::slice::Iter; /// A trait used to power [enum-like] operations via [reflection]. @@ -124,11 +124,6 @@ pub trait Enum: PartialReflect { fn variant_index(&self) -> usize; /// The type of the current variant. fn variant_type(&self) -> VariantType; - // Clones the enum into a [`DynamicEnum`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_enum` instead")] - fn clone_dynamic(&self) -> DynamicEnum { - self.to_dynamic_enum() - } /// Creates a new [`DynamicEnum`] from this enum. fn to_dynamic_enum(&self) -> DynamicEnum { DynamicEnum::from_ref(self) diff --git a/crates/bevy_reflect/src/enums/variants.rs b/crates/bevy_reflect/src/enums/variants.rs index 25e40334e7..55ccb8efb1 100644 --- a/crates/bevy_reflect/src/enums/variants.rs +++ b/crates/bevy_reflect/src/enums/variants.rs @@ -3,8 +3,8 @@ use crate::{ NamedField, UnnamedField, }; use alloc::boxed::Box; -use bevy_platform_support::collections::HashMap; -use bevy_platform_support::sync::Arc; +use bevy_platform::collections::HashMap; +use bevy_platform::sync::Arc; use core::slice::Iter; use thiserror::Error; diff --git a/crates/bevy_reflect/src/error.rs b/crates/bevy_reflect/src/error.rs index e783a33775..a13b55cdc0 100644 --- a/crates/bevy_reflect/src/error.rs +++ b/crates/bevy_reflect/src/error.rs @@ -55,7 +55,7 @@ fn full_path( container_type_path: &str, ) -> alloc::string::String { match variant { - Some(variant) => format!("{}::{}::{}", container_type_path, variant, field), - None => format!("{}::{}", container_type_path, field), + Some(variant) => format!("{container_type_path}::{variant}::{field}"), + None => format!("{container_type_path}::{field}"), } } diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index 393bfcb2e2..21d4ccd98a 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -4,7 +4,7 @@ use crate::{ MaybeTyped, PartialReflect, Type, TypeInfo, TypePath, }; use alloc::borrow::Cow; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use core::fmt::{Display, Formatter}; /// The named field of a reflected struct. diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index 9253921112..054e8ffaff 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -11,7 +11,7 @@ use crate::{ ReflectRef, TypeInfo, TypePath, }; use alloc::{borrow::Cow, boxed::Box}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use bevy_reflect_derive::impl_type_path; use core::fmt::{Debug, Formatter}; @@ -480,7 +480,7 @@ mod tests { use crate::func::{FunctionError, IntoReturn, SignatureInfo}; use crate::Type; use alloc::{format, string::String, vec, vec::Vec}; - use bevy_platform_support::collections::HashSet; + use bevy_platform::collections::HashSet; use core::ops::Add; #[test] @@ -550,7 +550,7 @@ mod tests { fn should_clone_dynamic_function() { let hello = String::from("Hello"); - let greet = |name: &String| -> String { format!("{}, {}!", hello, name) }; + let greet = |name: &String| -> String { format!("{hello}, {name}!") }; let greet = greet.into_function().with_name("greet"); let clone = greet.clone(); @@ -771,18 +771,18 @@ mod tests { #[test] fn should_debug_dynamic_function() { fn greet(name: &String) -> String { - format!("Hello, {}!", name) + format!("Hello, {name}!") } let function = greet.into_function(); - let debug = format!("{:?}", function); + let debug = format!("{function:?}"); assert_eq!(debug, "DynamicFunction(fn bevy_reflect::func::dynamic_function::tests::should_debug_dynamic_function::greet(_: &alloc::string::String) -> alloc::string::String)"); } #[test] fn should_debug_anonymous_dynamic_function() { let function = (|a: i32, b: i32| a + b).into_function(); - let debug = format!("{:?}", function); + let debug = format!("{function:?}"); assert_eq!(debug, "DynamicFunction(fn _(_: i32, _: i32) -> i32)"); } @@ -792,11 +792,11 @@ mod tests { a + b } - let func = add:: + let function = add:: .into_function() .with_overload(add::) .with_name("add"); - let debug = format!("{:?}", func); + let debug = format!("{function:?}"); assert_eq!( debug, "DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})" diff --git a/crates/bevy_reflect/src/func/dynamic_function_internal.rs b/crates/bevy_reflect/src/func/dynamic_function_internal.rs index 65b98e5e13..7e36ec119d 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_internal.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_internal.rs @@ -2,7 +2,7 @@ use crate::func::args::ArgCount; use crate::func::signature::{ArgListSignature, ArgumentSignature}; use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionOverloadError}; use alloc::{borrow::Cow, vec, vec::Vec}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::fmt::{Debug, Formatter}; /// An internal structure for storing a function and its corresponding [function information]. diff --git a/crates/bevy_reflect/src/func/dynamic_function_mut.rs b/crates/bevy_reflect/src/func/dynamic_function_mut.rs index b706ac620a..6d8be5ac47 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_mut.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_mut.rs @@ -1,5 +1,5 @@ use alloc::{borrow::Cow, boxed::Box}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use core::fmt::{Debug, Formatter}; use crate::func::{ diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index d4407d9698..d9d105db1b 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -4,7 +4,7 @@ use crate::func::{ Return, }; use alloc::borrow::Cow; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use thiserror::Error; /// An error that occurs when calling a [`DynamicFunction`] or [`DynamicFunctionMut`]. diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index eb770e9e50..29fa6bf7cf 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -63,12 +63,6 @@ pub trait Function: PartialReflect + Debug { /// Call this function with the given arguments. fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a>; - /// Clone this function into a [`DynamicFunction`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_function` instead")] - fn clone_dynamic(&self) -> DynamicFunction<'static> { - self.to_dynamic_function() - } - /// Creates a new [`DynamicFunction`] from this function. fn to_dynamic_function(&self) -> DynamicFunction<'static>; } diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index 53737fd891..4b130e5772 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -434,7 +434,7 @@ impl<'a> Debug for PrettyPrintFunctionInfo<'a> { } match (self.include_name, self.info.name()) { - (true, Some(name)) => write!(f, "{}", name)?, + (true, Some(name)) => write!(f, "{name}")?, (true, None) => write!(f, "_")?, _ => {} } @@ -509,7 +509,7 @@ impl<'a> Debug for PrettyPrintSignatureInfo<'a> { } match (self.include_name, self.info.name()) { - (true, Some(name)) => write!(f, "{}", name)?, + (true, Some(name)) => write!(f, "{name}")?, (true, None) => write!(f, "_")?, _ => {} } diff --git a/crates/bevy_reflect/src/func/registry.rs b/crates/bevy_reflect/src/func/registry.rs index d9a570dbe1..e476353b8b 100644 --- a/crates/bevy_reflect/src/func/registry.rs +++ b/crates/bevy_reflect/src/func/registry.rs @@ -1,5 +1,5 @@ use alloc::borrow::Cow; -use bevy_platform_support::{ +use bevy_platform::{ collections::HashMap, sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; @@ -520,7 +520,7 @@ mod tests { let mut registry = FunctionRegistry::default(); registry.register_with_name("foo", foo).unwrap(); - let debug = format!("{:?}", registry); + let debug = format!("{registry:?}"); assert_eq!(debug, "{DynamicFunction(fn foo() -> i32)}"); } } diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs index bab3c04b25..9abe0ef32c 100644 --- a/crates/bevy_reflect/src/func/return_type.rs +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -129,7 +129,7 @@ macro_rules! impl_into_return { )? { fn into_return<'into_return>(self) -> $crate::func::Return<'into_return> where Self: 'into_return { - $crate::func::Return::Owned(Box::new(self)) + $crate::func::Return::Owned(bevy_platform::prelude::Box::new(self)) } } diff --git a/crates/bevy_reflect/src/func/signature.rs b/crates/bevy_reflect/src/func/signature.rs index c8862b35d0..9102049eee 100644 --- a/crates/bevy_reflect/src/func/signature.rs +++ b/crates/bevy_reflect/src/func/signature.rs @@ -15,7 +15,7 @@ use crate::func::args::ArgInfo; use crate::func::{ArgList, SignatureInfo}; use crate::Type; use alloc::boxed::Box; -use bevy_platform_support::collections::Equivalent; +use bevy_platform::collections::Equivalent; use core::borrow::Borrow; use core::fmt::{Debug, Formatter}; use core::hash::{Hash, Hasher}; @@ -229,7 +229,7 @@ mod tests { ); assert_eq!( - format!("{:?}", signature), + format!("{signature:?}"), "(&mut alloc::string::String, i32) -> ()" ); } diff --git a/crates/bevy_reflect/src/generics.rs b/crates/bevy_reflect/src/generics.rs index dfe21c6ec0..8c9c4816ba 100644 --- a/crates/bevy_reflect/src/generics.rs +++ b/crates/bevy_reflect/src/generics.rs @@ -1,7 +1,7 @@ use crate::type_info::impl_type_methods; use crate::{Reflect, Type, TypePath}; use alloc::{borrow::Cow, boxed::Box}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use core::ops::Deref; use derive_more::derive::From; diff --git a/crates/bevy_reflect/src/impls/alloc/borrow.rs b/crates/bevy_reflect/src/impls/alloc/borrow.rs new file mode 100644 index 0000000000..9021343c67 --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/borrow.rs @@ -0,0 +1,308 @@ +use crate::{ + error::ReflectCloneError, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + list::{List, ListInfo, ListIter}, + prelude::*, + reflect::{impl_full_reflect, ApplyError}, + type_info::{MaybeTyped, OpaqueInfo, TypeInfo, Typed}, + type_registry::{ + FromType, GetTypeRegistration, ReflectDeserialize, ReflectFromPtr, ReflectSerialize, + TypeRegistration, TypeRegistry, + }, + utility::{reflect_hasher, GenericTypeInfoCell, NonGenericTypeInfoCell}, +}; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use bevy_platform::prelude::*; +use bevy_reflect_derive::impl_type_path; +use core::any::Any; +use core::fmt; +use core::hash::{Hash, Hasher}; + +impl_type_path!(::alloc::borrow::Cow<'a: 'static, T: ToOwned + ?Sized>); + +impl PartialReflect for Cow<'static, str> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + } else { + return Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + // If we invoke the reflect_type_path on self directly the borrow checker complains that the lifetime of self must outlive 'static + to_type: Self::type_path().into(), + }); + } + Ok(()) + } +} + +impl_full_reflect!(for Cow<'static, str>); + +impl Typed for Cow<'static, str> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl GetTypeRegistration for Cow<'static, str> { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::>(); + registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); + registration.insert::(FromType::>::from_type()); + registration + } +} + +impl FromReflect for Cow<'static, str> { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::>()?.clone()) + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(Cow<'static, str>); + +impl List + for Cow<'static, [T]> +{ + fn get(&self, index: usize) -> Option<&dyn PartialReflect> { + self.as_ref().get(index).map(|x| x as &dyn PartialReflect) + } + + fn get_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { + self.to_mut() + .get_mut(index) + .map(|x| x as &mut dyn PartialReflect) + } + + fn insert(&mut self, index: usize, element: Box) { + let value = T::take_from_reflect(element).unwrap_or_else(|value| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ); + }); + self.to_mut().insert(index, value); + } + + fn remove(&mut self, index: usize) -> Box { + Box::new(self.to_mut().remove(index)) + } + + fn push(&mut self, value: Box) { + let value = T::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to push invalid value of type {}.", + value.reflect_type_path() + ) + }); + self.to_mut().push(value); + } + + fn pop(&mut self) -> Option> { + self.to_mut() + .pop() + .map(|value| Box::new(value) as Box) + } + + fn len(&self) -> usize { + self.as_ref().len() + } + + fn iter(&self) -> ListIter { + ListIter::new(self) + } + + fn drain(&mut self) -> Vec> { + self.to_mut() + .drain(..) + .map(|value| Box::new(value) as Box) + .collect() + } +} + +impl PartialReflect + for Cow<'static, [T]> +{ + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::List + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::List(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::List(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::List(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) + } + + fn reflect_hash(&self) -> Option { + crate::list_hash(self) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + crate::list_partial_eq(self, value) + } + + fn apply(&mut self, value: &dyn PartialReflect) { + crate::list_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + crate::list_try_apply(self, value) + } +} + +impl_full_reflect!( + for Cow<'static, [T]> + where + T: FromReflect + Clone + MaybeTyped + TypePath + GetTypeRegistration, +); + +impl Typed + for Cow<'static, [T]> +{ + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) + } +} + +impl GetTypeRegistration + for Cow<'static, [T]> +{ + fn get_type_registration() -> TypeRegistration { + TypeRegistration::of::>() + } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } +} + +impl FromReflect + for Cow<'static, [T]> +{ + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + let ref_list = reflect.reflect_ref().as_list().ok()?; + + let mut temp_vec = Vec::with_capacity(ref_list.len()); + + for field in ref_list.iter() { + temp_vec.push(T::from_reflect(field)?); + } + + Some(temp_vec.into()) + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(Cow<'static, [T]>; ); diff --git a/crates/bevy_reflect/src/impls/alloc/collections/binary_heap.rs b/crates/bevy_reflect/src/impls/alloc/collections/binary_heap.rs new file mode 100644 index 0000000000..38f16f5851 --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/binary_heap.rs @@ -0,0 +1,28 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::alloc::collections::BinaryHeap(Clone)); + +#[cfg(test)] +mod tests { + use alloc::collections::BTreeMap; + use bevy_reflect::Reflect; + + #[test] + fn should_partial_eq_btree_map() { + let mut a = BTreeMap::new(); + a.insert(0usize, 1.23_f64); + let b = a.clone(); + let mut c = BTreeMap::new(); + c.insert(0usize, 3.21_f64); + + let a: &dyn Reflect = &a; + let b: &dyn Reflect = &b; + let c: &dyn Reflect = &c; + assert!(a + .reflect_partial_eq(b.as_partial_reflect()) + .unwrap_or_default()); + assert!(!a + .reflect_partial_eq(c.as_partial_reflect()) + .unwrap_or_default()); + } +} diff --git a/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs new file mode 100644 index 0000000000..e579ace206 --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs @@ -0,0 +1,254 @@ +use crate::{ + error::ReflectCloneError, + generics::{Generics, TypeParamInfo}, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + map::{map_apply, map_partial_eq, map_try_apply, Map, MapInfo, MapIter}, + prelude::*, + reflect::{impl_full_reflect, ApplyError}, + type_info::{MaybeTyped, TypeInfo, Typed}, + type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration}, + utility::GenericTypeInfoCell, +}; +use alloc::borrow::Cow; +use alloc::vec::Vec; +use bevy_platform::prelude::*; +use bevy_reflect_derive::impl_type_path; + +impl Map for ::alloc::collections::BTreeMap +where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +{ + fn get(&self, key: &dyn PartialReflect) -> Option<&dyn PartialReflect> { + key.try_downcast_ref::() + .and_then(|key| Self::get(self, key)) + .map(|value| value as &dyn PartialReflect) + } + + fn get_mut(&mut self, key: &dyn PartialReflect) -> Option<&mut dyn PartialReflect> { + key.try_downcast_ref::() + .and_then(move |key| Self::get_mut(self, key)) + .map(|value| value as &mut dyn PartialReflect) + } + + fn get_at(&self, index: usize) -> Option<(&dyn PartialReflect, &dyn PartialReflect)> { + self.iter() + .nth(index) + .map(|(key, value)| (key as &dyn PartialReflect, value as &dyn PartialReflect)) + } + + fn get_at_mut( + &mut self, + index: usize, + ) -> Option<(&dyn PartialReflect, &mut dyn PartialReflect)> { + self.iter_mut() + .nth(index) + .map(|(key, value)| (key as &dyn PartialReflect, value as &mut dyn PartialReflect)) + } + + fn len(&self) -> usize { + Self::len(self) + } + + fn iter(&self) -> MapIter { + MapIter::new(self) + } + + fn drain(&mut self) -> Vec<(Box, Box)> { + // BTreeMap doesn't have a `drain` function. See + // https://github.com/rust-lang/rust/issues/81074. So we have to fake one by popping + // elements off one at a time. + let mut result = Vec::with_capacity(self.len()); + while let Some((k, v)) = self.pop_first() { + result.push(( + Box::new(k) as Box, + Box::new(v) as Box, + )); + } + result + } + + fn insert_boxed( + &mut self, + key: Box, + value: Box, + ) -> Option> { + let key = K::take_from_reflect(key).unwrap_or_else(|key| { + panic!( + "Attempted to insert invalid key of type {}.", + key.reflect_type_path() + ) + }); + let value = V::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ) + }); + self.insert(key, value) + .map(|old_value| Box::new(old_value) as Box) + } + + fn remove(&mut self, key: &dyn PartialReflect) -> Option> { + let mut from_reflect = None; + key.try_downcast_ref::() + .or_else(|| { + from_reflect = K::from_reflect(key); + from_reflect.as_ref() + }) + .and_then(|key| self.remove(key)) + .map(|value| Box::new(value) as Box) + } +} + +impl PartialReflect for ::alloc::collections::BTreeMap +where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +{ + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + #[inline] + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Map + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Map(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Map(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Map(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + let mut map = Self::new(); + for (key, value) in self.iter() { + let key = + key.reflect_clone()? + .take() + .map_err(|_| ReflectCloneError::FailedDowncast { + expected: Cow::Borrowed(::type_path()), + received: Cow::Owned(key.reflect_type_path().to_string()), + })?; + let value = + value + .reflect_clone()? + .take() + .map_err(|_| ReflectCloneError::FailedDowncast { + expected: Cow::Borrowed(::type_path()), + received: Cow::Owned(value.reflect_type_path().to_string()), + })?; + map.insert(key, value); + } + + Ok(Box::new(map)) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + map_partial_eq(self, value) + } + + fn apply(&mut self, value: &dyn PartialReflect) { + map_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + map_try_apply(self, value) + } +} + +impl_full_reflect!( + for ::alloc::collections::BTreeMap + where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +); + +impl Typed for ::alloc::collections::BTreeMap +where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +{ + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + TypeInfo::Map( + MapInfo::new::().with_generics(Generics::from_iter([ + TypeParamInfo::new::("K"), + TypeParamInfo::new::("V"), + ])), + ) + }) + } +} + +impl GetTypeRegistration for ::alloc::collections::BTreeMap +where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +{ + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for ::alloc::collections::BTreeMap +where + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, +{ + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + let ref_map = reflect.reflect_ref().as_map().ok()?; + + let mut new_map = Self::new(); + + for (key, value) in ref_map.iter() { + let new_key = K::from_reflect(key)?; + let new_value = V::from_reflect(value)?; + new_map.insert(new_key, new_value); + } + + Some(new_map) + } +} + +impl_type_path!(::alloc::collections::BTreeMap); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::alloc::collections::BTreeMap; + < + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + > +); diff --git a/crates/bevy_reflect/src/impls/alloc/collections/btree/mod.rs b/crates/bevy_reflect/src/impls/alloc/collections/btree/mod.rs new file mode 100644 index 0000000000..095ca5dd2e --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/btree/mod.rs @@ -0,0 +1,2 @@ +mod map; +mod set; diff --git a/crates/bevy_reflect/src/impls/alloc/collections/btree/set.rs b/crates/bevy_reflect/src/impls/alloc/collections/btree/set.rs new file mode 100644 index 0000000000..41d711f205 --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/btree/set.rs @@ -0,0 +1,3 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::alloc::collections::BTreeSet(Clone)); diff --git a/crates/bevy_reflect/src/impls/alloc/collections/mod.rs b/crates/bevy_reflect/src/impls/alloc/collections/mod.rs new file mode 100644 index 0000000000..dcf75ce03c --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/mod.rs @@ -0,0 +1,3 @@ +mod binary_heap; +mod btree; +mod vec_deque; diff --git a/crates/bevy_reflect/src/impls/alloc/collections/vec_deque.rs b/crates/bevy_reflect/src/impls/alloc/collections/vec_deque.rs new file mode 100644 index 0000000000..5144bc3c2b --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/collections/vec_deque.rs @@ -0,0 +1,20 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_veclike; +#[cfg(feature = "functions")] +use crate::{ + from_reflect::FromReflect, type_info::MaybeTyped, type_path::TypePath, + type_registry::GetTypeRegistration, +}; + +impl_reflect_for_veclike!( + ::alloc::collections::VecDeque, + ::alloc::collections::VecDeque::insert, + ::alloc::collections::VecDeque::remove, + ::alloc::collections::VecDeque::push_back, + ::alloc::collections::VecDeque::pop_back, + ::alloc::collections::VecDeque:: +); +impl_type_path!(::alloc::collections::VecDeque); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::alloc::collections::VecDeque; ); diff --git a/crates/bevy_reflect/src/impls/alloc/mod.rs b/crates/bevy_reflect/src/impls/alloc/mod.rs new file mode 100644 index 0000000000..a49fa5599f --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/mod.rs @@ -0,0 +1,4 @@ +mod borrow; +mod collections; +mod string; +mod vec; diff --git a/crates/bevy_reflect/src/impls/alloc/string.rs b/crates/bevy_reflect/src/impls/alloc/string.rs new file mode 100644 index 0000000000..627082ee75 --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/string.rs @@ -0,0 +1,30 @@ +use crate::{ + std_traits::ReflectDefault, + type_registry::{ReflectDeserialize, ReflectSerialize}, +}; +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::alloc::string::String( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); + +#[cfg(test)] +mod tests { + use alloc::string::String; + use bevy_reflect::PartialReflect; + + #[test] + fn should_partial_eq_string() { + let a: &dyn PartialReflect = &String::from("Hello"); + let b: &dyn PartialReflect = &String::from("Hello"); + let c: &dyn PartialReflect = &String::from("World"); + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } +} diff --git a/crates/bevy_reflect/src/impls/alloc/vec.rs b/crates/bevy_reflect/src/impls/alloc/vec.rs new file mode 100644 index 0000000000..4e4c819d6a --- /dev/null +++ b/crates/bevy_reflect/src/impls/alloc/vec.rs @@ -0,0 +1,35 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_veclike; +#[cfg(feature = "functions")] +use crate::{ + from_reflect::FromReflect, type_info::MaybeTyped, type_path::TypePath, + type_registry::GetTypeRegistration, +}; + +impl_reflect_for_veclike!( + ::alloc::vec::Vec, + ::alloc::vec::Vec::insert, + ::alloc::vec::Vec::remove, + ::alloc::vec::Vec::push, + ::alloc::vec::Vec::pop, + [T] +); +impl_type_path!(::alloc::vec::Vec); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::alloc::vec::Vec; ); + +#[cfg(test)] +mod tests { + use alloc::vec; + use bevy_reflect::PartialReflect; + + #[test] + fn should_partial_eq_vec() { + let a: &dyn PartialReflect = &vec![1, 2, 3]; + let b: &dyn PartialReflect = &vec![1, 2, 3]; + let c: &dyn PartialReflect = &vec![3, 2, 1]; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } +} diff --git a/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_map.rs b/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_map.rs new file mode 100644 index 0000000000..48f0ddcd89 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_map.rs @@ -0,0 +1,47 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_hashmap; +#[cfg(feature = "functions")] +use crate::{ + from_reflect::FromReflect, type_info::MaybeTyped, type_path::TypePath, + type_registry::GetTypeRegistration, +}; +#[cfg(feature = "functions")] +use core::hash::{BuildHasher, Hash}; + +impl_reflect_for_hashmap!(bevy_platform::collections::HashMap); +impl_type_path!(::bevy_platform::collections::HashMap); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::bevy_platform::collections::HashMap; + < + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + +#[cfg(test)] +mod tests { + use crate::{PartialReflect, Reflect}; + use static_assertions::assert_impl_all; + + #[test] + fn should_partial_eq_hash_map() { + let mut a = >::default(); + a.insert(0usize, 1.23_f64); + let b = a.clone(); + let mut c = >::default(); + c.insert(0usize, 3.21_f64); + + let a: &dyn PartialReflect = &a; + let b: &dyn PartialReflect = &b; + let c: &dyn PartialReflect = &c; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } + + #[test] + fn should_reflect_hashmaps() { + assert_impl_all!(bevy_platform::collections::HashMap: Reflect); + } +} diff --git a/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_set.rs b/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_set.rs new file mode 100644 index 0000000000..a9c3f7a959 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/collections/hash_set.rs @@ -0,0 +1,17 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_hashset; +#[cfg(feature = "functions")] +use crate::{from_reflect::FromReflect, type_path::TypePath, type_registry::GetTypeRegistration}; +#[cfg(feature = "functions")] +use core::hash::{BuildHasher, Hash}; + +impl_reflect_for_hashset!(::bevy_platform::collections::HashSet); +impl_type_path!(::bevy_platform::collections::HashSet); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::bevy_platform::collections::HashSet; + < + V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); diff --git a/crates/bevy_reflect/src/impls/bevy_platform/collections/mod.rs b/crates/bevy_reflect/src/impls/bevy_platform/collections/mod.rs new file mode 100644 index 0000000000..2bde6a0653 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/collections/mod.rs @@ -0,0 +1,2 @@ +mod hash_map; +mod hash_set; diff --git a/crates/bevy_reflect/src/impls/bevy_platform/hash.rs b/crates/bevy_reflect/src/impls/bevy_platform/hash.rs new file mode 100644 index 0000000000..c2a38dd5f3 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/hash.rs @@ -0,0 +1,5 @@ +use bevy_reflect_derive::impl_type_path; + +impl_type_path!(::bevy_platform::hash::NoOpHash); +impl_type_path!(::bevy_platform::hash::FixedHasher); +impl_type_path!(::bevy_platform::hash::PassHash); diff --git a/crates/bevy_reflect/src/impls/bevy_platform/mod.rs b/crates/bevy_reflect/src/impls/bevy_platform/mod.rs new file mode 100644 index 0000000000..aa7f15eb5e --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/mod.rs @@ -0,0 +1,4 @@ +mod collections; +mod hash; +mod sync; +mod time; diff --git a/crates/bevy_reflect/src/impls/bevy_platform/sync.rs b/crates/bevy_reflect/src/impls/bevy_platform/sync.rs new file mode 100644 index 0000000000..ba3234177f --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/sync.rs @@ -0,0 +1,3 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::bevy_platform::sync::Arc(Clone)); diff --git a/crates/bevy_reflect/src/impls/bevy_platform/time.rs b/crates/bevy_reflect/src/impls/bevy_platform/time.rs new file mode 100644 index 0000000000..7120359dc4 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_platform/time.rs @@ -0,0 +1,18 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::bevy_platform::time::Instant( + Clone, Debug, Hash, PartialEq +)); + +#[cfg(test)] +mod tests { + use crate::FromReflect; + use bevy_platform::time::Instant; + + #[test] + fn instant_should_from_reflect() { + let expected = Instant::now(); + let output = Instant::from_reflect(&expected).unwrap(); + assert_eq!(expected, output); + } +} diff --git a/crates/bevy_reflect/src/impls/core/any.rs b/crates/bevy_reflect/src/impls/core/any.rs new file mode 100644 index 0000000000..062768c341 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/any.rs @@ -0,0 +1,15 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::core::any::TypeId(Clone, Debug, Hash, PartialEq,)); + +#[cfg(test)] +mod tests { + use bevy_reflect::FromReflect; + + #[test] + fn type_id_should_from_reflect() { + let type_id = core::any::TypeId::of::(); + let output = ::from_reflect(&type_id).unwrap(); + assert_eq!(type_id, output); + } +} diff --git a/crates/bevy_reflect/src/impls/core/hash.rs b/crates/bevy_reflect/src/impls/core/hash.rs new file mode 100644 index 0000000000..6301400091 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/hash.rs @@ -0,0 +1,3 @@ +use bevy_reflect_derive::impl_type_path; + +impl_type_path!(::core::hash::BuildHasherDefault); diff --git a/crates/bevy_reflect/src/impls/core/mod.rs b/crates/bevy_reflect/src/impls/core/mod.rs new file mode 100644 index 0000000000..cf20471410 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/mod.rs @@ -0,0 +1,11 @@ +mod any; +mod hash; +mod net; +mod num; +mod ops; +mod option; +mod panic; +mod primitives; +mod result; +mod sync; +mod time; diff --git a/crates/bevy_reflect/src/impls/core/net.rs b/crates/bevy_reflect/src/impls/core/net.rs new file mode 100644 index 0000000000..965308680b --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/net.rs @@ -0,0 +1,11 @@ +use crate::type_registry::{ReflectDeserialize, ReflectSerialize}; +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::core::net::SocketAddr( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); diff --git a/crates/bevy_reflect/src/impls/core/num.rs b/crates/bevy_reflect/src/impls/core/num.rs new file mode 100644 index 0000000000..e64744f69e --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/num.rs @@ -0,0 +1,115 @@ +use crate::type_registry::{ReflectDeserialize, ReflectSerialize}; +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::core::num::NonZeroI128( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroU128( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroIsize( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroUsize( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroI64( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroU64( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroU32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroI32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroI16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroU16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroU8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::NonZeroI8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_opaque!(::core::num::Wrapping(Clone)); +impl_reflect_opaque!(::core::num::Saturating(Clone)); + +#[cfg(test)] +mod tests { + use bevy_reflect::{FromReflect, PartialReflect}; + + #[test] + fn nonzero_usize_impl_reflect_from_reflect() { + let a: &dyn PartialReflect = &core::num::NonZero::::new(42).unwrap(); + let b: &dyn PartialReflect = &core::num::NonZero::::new(42).unwrap(); + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + let forty_two: core::num::NonZero = FromReflect::from_reflect(a).unwrap(); + assert_eq!(forty_two, core::num::NonZero::::new(42).unwrap()); + } +} diff --git a/crates/bevy_reflect/src/impls/core/ops.rs b/crates/bevy_reflect/src/impls/core/ops.rs new file mode 100644 index 0000000000..fdbe87a802 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/ops.rs @@ -0,0 +1,9 @@ +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::core::ops::Range(Clone)); +impl_reflect_opaque!(::core::ops::RangeInclusive(Clone)); +impl_reflect_opaque!(::core::ops::RangeFrom(Clone)); +impl_reflect_opaque!(::core::ops::RangeTo(Clone)); +impl_reflect_opaque!(::core::ops::RangeToInclusive(Clone)); +impl_reflect_opaque!(::core::ops::RangeFull(Clone)); +impl_reflect_opaque!(::core::ops::Bound(Clone)); diff --git a/crates/bevy_reflect/src/impls/core/option.rs b/crates/bevy_reflect/src/impls/core/option.rs new file mode 100644 index 0000000000..fe8318806d --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/option.rs @@ -0,0 +1,138 @@ +#![expect( + unused_qualifications, + reason = "the macro uses `MyEnum::Variant` which is generally unnecessary for `Option`" +)] + +use bevy_reflect_derive::impl_reflect; + +impl_reflect! { + #[type_path = "core::option"] + enum Option { + None, + Some(T), + } +} + +#[cfg(test)] +mod tests { + use crate::{Enum, FromReflect, PartialReflect, TypeInfo, Typed, VariantInfo, VariantType}; + use bevy_reflect_derive::Reflect; + use static_assertions::assert_impl_all; + + #[test] + fn should_partial_eq_option() { + let a: &dyn PartialReflect = &Some(123); + let b: &dyn PartialReflect = &Some(123); + assert_eq!(Some(true), a.reflect_partial_eq(b)); + } + + #[test] + fn option_should_impl_enum() { + assert_impl_all!(Option<()>: Enum); + + let mut value = Some(123usize); + + assert!(value + .reflect_partial_eq(&Some(123usize)) + .unwrap_or_default()); + assert!(!value + .reflect_partial_eq(&Some(321usize)) + .unwrap_or_default()); + + assert_eq!("Some", value.variant_name()); + assert_eq!("core::option::Option::Some", value.variant_path()); + + if value.is_variant(VariantType::Tuple) { + if let Some(field) = value + .field_at_mut(0) + .and_then(|field| field.try_downcast_mut::()) + { + *field = 321; + } + } else { + panic!("expected `VariantType::Tuple`"); + } + + assert_eq!(Some(321), value); + } + + #[test] + fn option_should_from_reflect() { + #[derive(Reflect, PartialEq, Debug)] + struct Foo(usize); + + let expected = Some(Foo(123)); + let output = as FromReflect>::from_reflect(&expected).unwrap(); + + assert_eq!(expected, output); + } + + #[test] + fn option_should_apply() { + #[derive(Reflect, PartialEq, Debug)] + struct Foo(usize); + + // === None on None === // + let patch = None::; + let mut value = None::; + PartialReflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "None apply onto None"); + + // === Some on None === // + let patch = Some(Foo(123)); + let mut value = None::; + PartialReflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "Some apply onto None"); + + // === None on Some === // + let patch = None::; + let mut value = Some(Foo(321)); + PartialReflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "None apply onto Some"); + + // === Some on Some === // + let patch = Some(Foo(123)); + let mut value = Some(Foo(321)); + PartialReflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "Some apply onto Some"); + } + + #[test] + fn option_should_impl_typed() { + assert_impl_all!(Option<()>: Typed); + + type MyOption = Option; + let info = MyOption::type_info(); + if let TypeInfo::Enum(info) = info { + assert_eq!( + "None", + info.variant_at(0).unwrap().name(), + "Expected `None` to be variant at index `0`" + ); + assert_eq!( + "Some", + info.variant_at(1).unwrap().name(), + "Expected `Some` to be variant at index `1`" + ); + assert_eq!("Some", info.variant("Some").unwrap().name()); + if let VariantInfo::Tuple(variant) = info.variant("Some").unwrap() { + assert!( + variant.field_at(0).unwrap().is::(), + "Expected `Some` variant to contain `i32`" + ); + assert!( + variant.field_at(1).is_none(), + "Expected `Some` variant to only contain 1 field" + ); + } else { + panic!("Expected `VariantInfo::Tuple`"); + } + } else { + panic!("Expected `TypeInfo::Enum`"); + } + } +} diff --git a/crates/bevy_reflect/src/impls/core/panic.rs b/crates/bevy_reflect/src/impls/core/panic.rs new file mode 100644 index 0000000000..75bf365422 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/panic.rs @@ -0,0 +1,158 @@ +use crate::{ + error::ReflectCloneError, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + prelude::*, + reflect::ApplyError, + type_info::{OpaqueInfo, TypeInfo, Typed}, + type_path::DynamicTypePath, + type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration}, + utility::{reflect_hasher, NonGenericTypeInfoCell}, +}; +use bevy_platform::prelude::*; +use core::any::Any; +use core::hash::{Hash, Hasher}; +use core::panic::Location; + +impl TypePath for &'static Location<'static> { + fn type_path() -> &'static str { + "core::panic::Location" + } + + fn short_type_path() -> &'static str { + "Location" + } +} + +impl PartialReflect for &'static Location<'static> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(*self)) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + Ok(()) + } else { + Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: ::reflect_type_path(self).into(), + }) + } + } +} + +impl Reflect for &'static Location<'static> { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl Typed for &'static Location<'static> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl GetTypeRegistration for &'static Location<'static> { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for &'static Location<'static> { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + reflect.try_downcast_ref::().copied() + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(&'static Location<'static>); diff --git a/crates/bevy_reflect/src/impls/core/primitives.rs b/crates/bevy_reflect/src/impls/core/primitives.rs new file mode 100644 index 0000000000..8bee33b4c8 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/primitives.rs @@ -0,0 +1,564 @@ +use crate::{ + array::{Array, ArrayInfo, ArrayIter}, + error::ReflectCloneError, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + prelude::*, + reflect::ApplyError, + type_info::{MaybeTyped, OpaqueInfo, TypeInfo, Typed}, + type_registry::{ + FromType, GetTypeRegistration, ReflectDeserialize, ReflectFromPtr, ReflectSerialize, + TypeRegistration, TypeRegistry, + }, + utility::{reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell}, +}; +use bevy_platform::prelude::*; +use bevy_reflect_derive::{impl_reflect_opaque, impl_type_path}; +use core::any::Any; +use core::fmt; +use core::hash::{Hash, Hasher}; + +impl_reflect_opaque!(bool( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(char( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(u8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(u16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(u32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(u64( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(u128( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(usize( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(i8( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(i16( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(i32( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(i64( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(i128( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(isize( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(f32( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_reflect_opaque!(f64( + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + Default +)); +impl_type_path!(str); + +impl PartialReflect for &'static str { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(*self)) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self, f) + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + } else { + return Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: Self::type_path().into(), + }); + } + Ok(()) + } +} + +impl Reflect for &'static str { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl Typed for &'static str { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl GetTypeRegistration for &'static str { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for &'static str { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + reflect.try_downcast_ref::().copied() + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(&'static str); + +impl Array for [T; N] { + #[inline] + fn get(&self, index: usize) -> Option<&dyn PartialReflect> { + <[T]>::get(self, index).map(|value| value as &dyn PartialReflect) + } + + #[inline] + fn get_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { + <[T]>::get_mut(self, index).map(|value| value as &mut dyn PartialReflect) + } + + #[inline] + fn len(&self) -> usize { + N + } + + #[inline] + fn iter(&self) -> ArrayIter { + ArrayIter::new(self) + } + + #[inline] + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } +} + +impl PartialReflect + for [T; N] +{ + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + #[inline] + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Array + } + + #[inline] + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Array(self) + } + + #[inline] + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Array(self) + } + + #[inline] + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Array(self) + } + + #[inline] + fn reflect_hash(&self) -> Option { + crate::array_hash(self) + } + + #[inline] + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + crate::array_partial_eq(self, value) + } + + fn apply(&mut self, value: &dyn PartialReflect) { + crate::array_apply(self, value); + } + + #[inline] + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + crate::array_try_apply(self, value) + } +} + +impl Reflect for [T; N] { + #[inline] + fn into_any(self: Box) -> Box { + self + } + + #[inline] + fn as_any(&self) -> &dyn Any { + self + } + + #[inline] + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + #[inline] + fn into_reflect(self: Box) -> Box { + self + } + + #[inline] + fn as_reflect(&self) -> &dyn Reflect { + self + } + + #[inline] + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + #[inline] + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl FromReflect + for [T; N] +{ + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + let ref_array = reflect.reflect_ref().as_array().ok()?; + + let mut temp_vec = Vec::with_capacity(ref_array.len()); + + for field in ref_array.iter() { + temp_vec.push(T::from_reflect(field)?); + } + + temp_vec.try_into().ok() + } +} + +impl Typed for [T; N] { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::Array(ArrayInfo::new::(N))) + } +} + +impl TypePath for [T; N] { + fn type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("[{t}; {N}]", t = T::type_path())) + } + + fn short_type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("[{t}; {N}]", t = T::short_type_path())) + } +} + +impl GetTypeRegistration + for [T; N] +{ + fn get_type_registration() -> TypeRegistration { + TypeRegistration::of::<[T; N]>() + } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!([T; N]; [const N: usize]); + +impl TypePath for [T] +where + [T]: ToOwned, +{ + fn type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("[{}]", ::type_path())) + } + + fn short_type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("[{}]", ::short_type_path())) + } +} + +impl TypePath for &'static T { + fn type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("&{}", T::type_path())) + } + + fn short_type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("&{}", T::short_type_path())) + } +} + +impl TypePath for &'static mut T { + fn type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("&mut {}", T::type_path())) + } + + fn short_type_path() -> &'static str { + static CELL: GenericTypePathCell = GenericTypePathCell::new(); + CELL.get_or_insert::(|| format!("&mut {}", T::short_type_path())) + } +} + +#[cfg(test)] +mod tests { + use bevy_reflect::{FromReflect, PartialReflect}; + use core::f32::consts::{PI, TAU}; + + #[test] + fn should_partial_eq_char() { + let a: &dyn PartialReflect = &'x'; + let b: &dyn PartialReflect = &'x'; + let c: &dyn PartialReflect = &'o'; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } + + #[test] + fn should_partial_eq_i32() { + let a: &dyn PartialReflect = &123_i32; + let b: &dyn PartialReflect = &123_i32; + let c: &dyn PartialReflect = &321_i32; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } + + #[test] + fn should_partial_eq_f32() { + let a: &dyn PartialReflect = &PI; + let b: &dyn PartialReflect = &PI; + let c: &dyn PartialReflect = &TAU; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } + + #[test] + fn static_str_should_from_reflect() { + let expected = "Hello, World!"; + let output = <&'static str as FromReflect>::from_reflect(&expected).unwrap(); + assert_eq!(expected, output); + } +} diff --git a/crates/bevy_reflect/src/impls/core/result.rs b/crates/bevy_reflect/src/impls/core/result.rs new file mode 100644 index 0000000000..d601e0bd6f --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/result.rs @@ -0,0 +1,14 @@ +#![expect( + unused_qualifications, + reason = "the macro uses `MyEnum::Variant` which is generally unnecessary for `Result`" +)] + +use bevy_reflect_derive::impl_reflect; + +impl_reflect! { + #[type_path = "core::result"] + enum Result { + Ok(T), + Err(E), + } +} diff --git a/crates/bevy_reflect/src/impls/core/sync.rs b/crates/bevy_reflect/src/impls/core/sync.rs new file mode 100644 index 0000000000..b4fe8977d5 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/sync.rs @@ -0,0 +1,192 @@ +use crate::{ + error::ReflectCloneError, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + prelude::*, + reflect::{impl_full_reflect, ApplyError}, + type_info::{OpaqueInfo, TypeInfo, Typed}, + type_path::DynamicTypePath, + type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration}, + utility::NonGenericTypeInfoCell, +}; +use bevy_platform::prelude::*; +use bevy_reflect_derive::impl_type_path; +use core::any::Any; +use core::fmt; + +macro_rules! impl_reflect_for_atomic { + ($ty:ty, $ordering:expr) => { + impl_type_path!($ty); + + const _: () = { + #[cfg(feature = "functions")] + crate::func::macros::impl_function_traits!($ty); + + impl GetTypeRegistration for $ty + where + $ty: Any + Send + Sync, + { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + + // Serde only supports atomic types when the "std" feature is enabled + #[cfg(feature = "std")] + { + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + } + + registration + } + } + + impl Typed for $ty + where + $ty: Any + Send + Sync, + { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| { + let info = OpaqueInfo::new::(); + TypeInfo::Opaque(info) + }) + } + } + + impl PartialReflect for $ty + where + $ty: Any + Send + Sync, + { + #[inline] + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + #[inline] + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + #[inline] + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + #[inline] + fn try_into_reflect( + self: Box, + ) -> Result, Box> { + Ok(self) + } + #[inline] + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + #[inline] + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + #[inline] + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(<$ty>::new(self.load($ordering)))) + } + + #[inline] + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + *self = <$ty>::new(value.load($ordering)); + } else { + return Err(ApplyError::MismatchedTypes { + from_type: Into::into(DynamicTypePath::reflect_type_path(value)), + to_type: Into::into(::type_path()), + }); + } + Ok(()) + } + #[inline] + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + #[inline] + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + #[inline] + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + #[inline] + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } + } + + impl FromReflect for $ty + where + $ty: Any + Send + Sync, + { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(<$ty>::new( + reflect.try_downcast_ref::<$ty>()?.load($ordering), + )) + } + } + }; + + impl_full_reflect!(for $ty where $ty: Any + Send + Sync); + }; +} + +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicIsize, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicUsize, + ::core::sync::atomic::Ordering::SeqCst +); +#[cfg(target_has_atomic = "64")] +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicI64, + ::core::sync::atomic::Ordering::SeqCst +); +#[cfg(target_has_atomic = "64")] +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicU64, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicI32, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicU32, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicI16, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicU16, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicI8, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicU8, + ::core::sync::atomic::Ordering::SeqCst +); +impl_reflect_for_atomic!( + ::core::sync::atomic::AtomicBool, + ::core::sync::atomic::Ordering::SeqCst +); diff --git a/crates/bevy_reflect/src/impls/core/time.rs b/crates/bevy_reflect/src/impls/core/time.rs new file mode 100644 index 0000000000..42e581b864 --- /dev/null +++ b/crates/bevy_reflect/src/impls/core/time.rs @@ -0,0 +1,32 @@ +use crate::{ + std_traits::ReflectDefault, + type_registry::{ReflectDeserialize, ReflectSerialize}, +}; +use bevy_reflect_derive::impl_reflect_opaque; + +impl_reflect_opaque!(::core::time::Duration( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); + +#[cfg(test)] +mod tests { + use bevy_reflect::{ReflectSerialize, TypeRegistry}; + use core::time::Duration; + + #[test] + fn can_serialize_duration() { + let mut type_registry = TypeRegistry::default(); + type_registry.register::(); + + let reflect_serialize = type_registry + .get_type_data::(core::any::TypeId::of::()) + .unwrap(); + let _serializable = reflect_serialize.get_serializable(&Duration::ZERO); + } +} diff --git a/crates/bevy_reflect/src/impls/hashbrown.rs b/crates/bevy_reflect/src/impls/hashbrown.rs new file mode 100644 index 0000000000..4ea914dc3f --- /dev/null +++ b/crates/bevy_reflect/src/impls/hashbrown.rs @@ -0,0 +1,43 @@ +use crate::impls::macros::{impl_reflect_for_hashmap, impl_reflect_for_hashset}; +#[cfg(feature = "functions")] +use crate::{ + from_reflect::FromReflect, type_info::MaybeTyped, type_path::TypePath, + type_registry::GetTypeRegistration, +}; +use bevy_reflect_derive::impl_type_path; +#[cfg(feature = "functions")] +use core::hash::{BuildHasher, Hash}; + +impl_reflect_for_hashmap!(hashbrown::hash_map::HashMap); +impl_type_path!(::hashbrown::hash_map::HashMap); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::hashbrown::hash_map::HashMap; + < + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + +impl_reflect_for_hashset!(::hashbrown::hash_set::HashSet); +impl_type_path!(::hashbrown::hash_set::HashSet); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::hashbrown::hash_set::HashSet; + < + V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + +#[cfg(test)] +mod tests { + use crate::Reflect; + use static_assertions::assert_impl_all; + + #[test] + fn should_reflect_hashmaps() { + // We specify `foldhash::fast::RandomState` directly here since without the `default-hasher` + // feature, hashbrown uses an empty enum to force users to specify their own + assert_impl_all!(hashbrown::HashMap: Reflect); + } +} diff --git a/crates/bevy_reflect/src/impls/macros/list.rs b/crates/bevy_reflect/src/impls/macros/list.rs new file mode 100644 index 0000000000..72e05ba10f --- /dev/null +++ b/crates/bevy_reflect/src/impls/macros/list.rs @@ -0,0 +1,192 @@ +macro_rules! impl_reflect_for_veclike { + ($ty:ty, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { + const _: () = { + impl $crate::list::List for $ty { + #[inline] + fn get(&self, index: usize) -> Option<&dyn $crate::reflect::PartialReflect> { + <$sub>::get(self, index).map(|value| value as &dyn $crate::reflect::PartialReflect) + } + + #[inline] + fn get_mut(&mut self, index: usize) -> Option<&mut dyn $crate::reflect::PartialReflect> { + <$sub>::get_mut(self, index).map(|value| value as &mut dyn $crate::reflect::PartialReflect) + } + + fn insert(&mut self, index: usize, value: bevy_platform::prelude::Box) { + let value = value.try_take::().unwrap_or_else(|value| { + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ) + }) + }); + $insert(self, index, value); + } + + fn remove(&mut self, index: usize) -> bevy_platform::prelude::Box { + bevy_platform::prelude::Box::new($remove(self, index)) + } + + fn push(&mut self, value: bevy_platform::prelude::Box) { + let value = T::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to push invalid value of type {}.", + value.reflect_type_path() + ) + }); + $push(self, value); + } + + fn pop(&mut self) -> Option> { + $pop(self).map(|value| bevy_platform::prelude::Box::new(value) as bevy_platform::prelude::Box) + } + + #[inline] + fn len(&self) -> usize { + <$sub>::len(self) + } + + #[inline] + fn iter(&self) -> $crate::list::ListIter { + $crate::list::ListIter::new(self) + } + + #[inline] + fn drain(&mut self) -> alloc::vec::Vec> { + self.drain(..) + .map(|value| bevy_platform::prelude::Box::new(value) as bevy_platform::prelude::Box) + .collect() + } + } + + impl $crate::reflect::PartialReflect for $ty { + #[inline] + fn get_represented_type_info(&self) -> Option<&'static $crate::type_info::TypeInfo> { + Some(::type_info()) + } + + fn into_partial_reflect(self: bevy_platform::prelude::Box) -> bevy_platform::prelude::Box { + self + } + + #[inline] + fn as_partial_reflect(&self) -> &dyn $crate::reflect::PartialReflect { + self + } + + #[inline] + fn as_partial_reflect_mut(&mut self) -> &mut dyn $crate::reflect::PartialReflect { + self + } + + fn try_into_reflect( + self: bevy_platform::prelude::Box, + ) -> Result, bevy_platform::prelude::Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn $crate::reflect::Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn $crate::reflect::Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> $crate::kind::ReflectKind { + $crate::kind::ReflectKind::List + } + + fn reflect_ref(&self) -> $crate::kind::ReflectRef { + $crate::kind::ReflectRef::List(self) + } + + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + $crate::kind::ReflectMut::List(self) + } + + fn reflect_owned(self: bevy_platform::prelude::Box) -> $crate::kind::ReflectOwned { + $crate::kind::ReflectOwned::List(self) + } + + fn reflect_clone(&self) -> Result, $crate::error::ReflectCloneError> { + Ok(bevy_platform::prelude::Box::new( + self.iter() + .map(|value| { + value.reflect_clone()?.take().map_err(|_| { + $crate::error::ReflectCloneError::FailedDowncast { + expected: alloc::borrow::Cow::Borrowed(::type_path()), + received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())), + } + }) + }) + .collect::>()?, + )) + } + + fn reflect_hash(&self) -> Option { + $crate::list::list_hash(self) + } + + fn reflect_partial_eq(&self, value: &dyn $crate::reflect::PartialReflect) -> Option { + $crate::list::list_partial_eq(self, value) + } + + fn apply(&mut self, value: &dyn $crate::reflect::PartialReflect) { + $crate::list::list_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn $crate::reflect::PartialReflect) -> Result<(), $crate::reflect::ApplyError> { + $crate::list::list_try_apply(self, value) + } + } + + $crate::impl_full_reflect!( for $ty where T: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration); + + impl $crate::type_info::Typed for $ty { + fn type_info() -> &'static $crate::type_info::TypeInfo { + static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + $crate::type_info::TypeInfo::List( + $crate::list::ListInfo::new::().with_generics($crate::generics::Generics::from_iter([ + $crate::generics::TypeParamInfo::new::("T") + ])) + ) + }) + } + } + + impl $crate::type_registry::GetTypeRegistration + for $ty + { + fn get_type_registration() -> $crate::type_registry::TypeRegistration { + let mut registration = $crate::type_registry::TypeRegistration::of::<$ty>(); + registration.insert::<$crate::type_registry::ReflectFromPtr>($crate::type_registry::FromType::<$ty>::from_type()); + registration.insert::<$crate::from_reflect::ReflectFromReflect>($crate::type_registry::FromType::<$ty>::from_type()); + registration + } + + fn register_type_dependencies(registry: &mut $crate::type_registry::TypeRegistry) { + registry.register::(); + } + } + + impl $crate::from_reflect::FromReflect for $ty { + fn from_reflect(reflect: &dyn $crate::reflect::PartialReflect) -> Option { + let ref_list = reflect.reflect_ref().as_list().ok()?; + + let mut new_list = Self::with_capacity(ref_list.len()); + + for field in ref_list.iter() { + $push(&mut new_list, T::from_reflect(field)?); + } + + Some(new_list) + } + } + }; + }; +} + +pub(crate) use impl_reflect_for_veclike; diff --git a/crates/bevy_reflect/src/impls/macros/map.rs b/crates/bevy_reflect/src/impls/macros/map.rs new file mode 100644 index 0000000000..ce621b7f78 --- /dev/null +++ b/crates/bevy_reflect/src/impls/macros/map.rs @@ -0,0 +1,261 @@ +macro_rules! impl_reflect_for_hashmap { + ($ty:path) => { + const _: () = { + impl $crate::map::Map for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn get(&self, key: &dyn $crate::reflect::PartialReflect) -> Option<&dyn $crate::reflect::PartialReflect> { + key.try_downcast_ref::() + .and_then(|key| Self::get(self, key)) + .map(|value| value as &dyn $crate::reflect::PartialReflect) + } + + fn get_mut(&mut self, key: &dyn $crate::reflect::PartialReflect) -> Option<&mut dyn $crate::reflect::PartialReflect> { + key.try_downcast_ref::() + .and_then(move |key| Self::get_mut(self, key)) + .map(|value| value as &mut dyn $crate::reflect::PartialReflect) + } + + fn get_at(&self, index: usize) -> Option<(&dyn $crate::reflect::PartialReflect, &dyn $crate::reflect::PartialReflect)> { + self.iter() + .nth(index) + .map(|(key, value)| (key as &dyn $crate::reflect::PartialReflect, value as &dyn $crate::reflect::PartialReflect)) + } + + fn get_at_mut( + &mut self, + index: usize, + ) -> Option<(&dyn $crate::reflect::PartialReflect, &mut dyn $crate::reflect::PartialReflect)> { + self.iter_mut().nth(index).map(|(key, value)| { + (key as &dyn $crate::reflect::PartialReflect, value as &mut dyn $crate::reflect::PartialReflect) + }) + } + + fn len(&self) -> usize { + Self::len(self) + } + + fn iter(&self) -> $crate::map::MapIter { + $crate::map::MapIter::new(self) + } + + fn drain(&mut self) -> bevy_platform::prelude::Vec<(bevy_platform::prelude::Box, bevy_platform::prelude::Box)> { + self.drain() + .map(|(key, value)| { + ( + bevy_platform::prelude::Box::new(key) as bevy_platform::prelude::Box, + bevy_platform::prelude::Box::new(value) as bevy_platform::prelude::Box, + ) + }) + .collect() + } + + fn to_dynamic_map(&self) -> $crate::map::DynamicMap { + let mut dynamic_map = $crate::map::DynamicMap::default(); + dynamic_map.set_represented_type($crate::reflect::PartialReflect::get_represented_type_info(self)); + for (k, v) in self { + let key = K::from_reflect(k).unwrap_or_else(|| { + panic!( + "Attempted to clone invalid key of type {}.", + k.reflect_type_path() + ) + }); + dynamic_map.insert_boxed(bevy_platform::prelude::Box::new(key), v.to_dynamic()); + } + dynamic_map + } + + fn insert_boxed( + &mut self, + key: bevy_platform::prelude::Box, + value: bevy_platform::prelude::Box, + ) -> Option> { + let key = K::take_from_reflect(key).unwrap_or_else(|key| { + panic!( + "Attempted to insert invalid key of type {}.", + key.reflect_type_path() + ) + }); + let value = V::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ) + }); + self.insert(key, value) + .map(|old_value| bevy_platform::prelude::Box::new(old_value) as bevy_platform::prelude::Box) + } + + fn remove(&mut self, key: &dyn $crate::reflect::PartialReflect) -> Option> { + let mut from_reflect = None; + key.try_downcast_ref::() + .or_else(|| { + from_reflect = K::from_reflect(key); + from_reflect.as_ref() + }) + .and_then(|key| self.remove(key)) + .map(|value| bevy_platform::prelude::Box::new(value) as bevy_platform::prelude::Box) + } + } + + impl $crate::reflect::PartialReflect for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn get_represented_type_info(&self) -> Option<&'static $crate::type_info::TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: bevy_platform::prelude::Box) -> bevy_platform::prelude::Box { + self + } + + fn as_partial_reflect(&self) -> &dyn $crate::reflect::PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn $crate::reflect::PartialReflect { + self + } + + fn try_into_reflect( + self: bevy_platform::prelude::Box, + ) -> Result, bevy_platform::prelude::Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn $crate::reflect::Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn $crate::reflect::Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> $crate::kind::ReflectKind { + $crate::kind::ReflectKind::Map + } + + fn reflect_ref(&self) -> $crate::kind::ReflectRef { + $crate::kind::ReflectRef::Map(self) + } + + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + $crate::kind::ReflectMut::Map(self) + } + + fn reflect_owned(self: bevy_platform::prelude::Box) -> $crate::kind::ReflectOwned { + $crate::kind::ReflectOwned::Map(self) + } + + fn reflect_clone(&self) -> Result, $crate::error::ReflectCloneError> { + let mut map = Self::with_capacity_and_hasher(self.len(), S::default()); + for (key, value) in self.iter() { + let key = key.reflect_clone()?.take().map_err(|_| { + $crate::error::ReflectCloneError::FailedDowncast { + expected: alloc::borrow::Cow::Borrowed(::type_path()), + received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(key.reflect_type_path())), + } + })?; + let value = value.reflect_clone()?.take().map_err(|_| { + $crate::error::ReflectCloneError::FailedDowncast { + expected: alloc::borrow::Cow::Borrowed(::type_path()), + received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())), + } + })?; + map.insert(key, value); + } + + Ok(bevy_platform::prelude::Box::new(map)) + } + + fn reflect_partial_eq(&self, value: &dyn $crate::reflect::PartialReflect) -> Option { + $crate::map::map_partial_eq(self, value) + } + + fn apply(&mut self, value: &dyn $crate::reflect::PartialReflect) { + $crate::map::map_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn $crate::reflect::PartialReflect) -> Result<(), $crate::reflect::ApplyError> { + $crate::map::map_try_apply(self, value) + } + } + + $crate::impl_full_reflect!( + for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + ); + + impl $crate::type_info::Typed for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn type_info() -> &'static $crate::type_info::TypeInfo { + static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + $crate::type_info::TypeInfo::Map( + $crate::map::MapInfo::new::().with_generics($crate::generics::Generics::from_iter([ + $crate::generics::TypeParamInfo::new::("K"), + $crate::generics::TypeParamInfo::new::("V"), + ])), + ) + }) + } + } + + impl $crate::type_registry::GetTypeRegistration for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync + Default, + { + fn get_type_registration() -> $crate::type_registry::TypeRegistration { + let mut registration = $crate::type_registry::TypeRegistration::of::(); + registration.insert::<$crate::type_registry::ReflectFromPtr>($crate::type_registry::FromType::::from_type()); + registration.insert::<$crate::from_reflect::ReflectFromReflect>($crate::type_registry::FromType::::from_type()); + registration + } + + fn register_type_dependencies(registry: &mut $crate::type_registry::TypeRegistry) { + registry.register::(); + registry.register::(); + } + } + + impl $crate::from_reflect::FromReflect for $ty + where + K: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + V: $crate::from_reflect::FromReflect + $crate::type_info::MaybeTyped + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn from_reflect(reflect: &dyn $crate::reflect::PartialReflect) -> Option { + let ref_map = reflect.reflect_ref().as_map().ok()?; + + let mut new_map = Self::with_capacity_and_hasher(ref_map.len(), S::default()); + + for (key, value) in ref_map.iter() { + let new_key = K::from_reflect(key)?; + let new_value = V::from_reflect(value)?; + new_map.insert(new_key, new_value); + } + + Some(new_map) + } + } + }; + }; +} + +pub(crate) use impl_reflect_for_hashmap; diff --git a/crates/bevy_reflect/src/impls/macros/mod.rs b/crates/bevy_reflect/src/impls/macros/mod.rs new file mode 100644 index 0000000000..1e57c39626 --- /dev/null +++ b/crates/bevy_reflect/src/impls/macros/mod.rs @@ -0,0 +1,7 @@ +pub(crate) use list::*; +pub(crate) use map::*; +pub(crate) use set::*; + +mod list; +mod map; +mod set; diff --git a/crates/bevy_reflect/src/impls/macros/set.rs b/crates/bevy_reflect/src/impls/macros/set.rs new file mode 100644 index 0000000000..e00e764e17 --- /dev/null +++ b/crates/bevy_reflect/src/impls/macros/set.rs @@ -0,0 +1,208 @@ +macro_rules! impl_reflect_for_hashset { + ($ty:path) => { + const _: () = { + impl $crate::set::Set for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn get(&self, value: &dyn $crate::reflect::PartialReflect) -> Option<&dyn $crate::reflect::PartialReflect> { + value + .try_downcast_ref::() + .and_then(|value| Self::get(self, value)) + .map(|value| value as &dyn $crate::reflect::PartialReflect) + } + + fn len(&self) -> usize { + Self::len(self) + } + + fn iter(&self) -> bevy_platform::prelude::Box + '_> { + let iter = self.iter().map(|v| v as &dyn $crate::reflect::PartialReflect); + bevy_platform::prelude::Box::new(iter) + } + + fn drain(&mut self) -> bevy_platform::prelude::Vec> { + self.drain() + .map(|value| bevy_platform::prelude::Box::new(value) as bevy_platform::prelude::Box) + .collect() + } + + fn insert_boxed(&mut self, value: bevy_platform::prelude::Box) -> bool { + let value = V::take_from_reflect(value).unwrap_or_else(|value| { + panic!( + "Attempted to insert invalid value of type {}.", + value.reflect_type_path() + ) + }); + self.insert(value) + } + + fn remove(&mut self, value: &dyn $crate::reflect::PartialReflect) -> bool { + let mut from_reflect = None; + value + .try_downcast_ref::() + .or_else(|| { + from_reflect = V::from_reflect(value); + from_reflect.as_ref() + }) + .is_some_and(|value| self.remove(value)) + } + + fn contains(&self, value: &dyn $crate::reflect::PartialReflect) -> bool { + let mut from_reflect = None; + value + .try_downcast_ref::() + .or_else(|| { + from_reflect = V::from_reflect(value); + from_reflect.as_ref() + }) + .is_some_and(|value| self.contains(value)) + } + } + + impl $crate::reflect::PartialReflect for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn get_represented_type_info(&self) -> Option<&'static $crate::type_info::TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: bevy_platform::prelude::Box) -> bevy_platform::prelude::Box { + self + } + + fn as_partial_reflect(&self) -> &dyn $crate::reflect::PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn $crate::reflect::PartialReflect { + self + } + + #[inline] + fn try_into_reflect( + self: bevy_platform::prelude::Box, + ) -> Result, bevy_platform::prelude::Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn $crate::reflect::Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn $crate::reflect::Reflect> { + Some(self) + } + + fn apply(&mut self, value: &dyn $crate::reflect::PartialReflect) { + $crate::set::set_apply(self, value); + } + + fn try_apply(&mut self, value: &dyn $crate::reflect::PartialReflect) -> Result<(), $crate::reflect::ApplyError> { + $crate::set::set_try_apply(self, value) + } + + fn reflect_kind(&self) -> $crate::kind::ReflectKind { + $crate::kind::ReflectKind::Set + } + + fn reflect_ref(&self) -> $crate::kind::ReflectRef { + $crate::kind::ReflectRef::Set(self) + } + + fn reflect_mut(&mut self) -> $crate::kind::ReflectMut { + $crate::kind::ReflectMut::Set(self) + } + + fn reflect_owned(self: bevy_platform::prelude::Box) -> $crate::kind::ReflectOwned { + $crate::kind::ReflectOwned::Set(self) + } + + fn reflect_clone(&self) -> Result, $crate::error::ReflectCloneError> { + let mut set = Self::with_capacity_and_hasher(self.len(), S::default()); + for value in self.iter() { + let value = value.reflect_clone()?.take().map_err(|_| { + $crate::error::ReflectCloneError::FailedDowncast { + expected: alloc::borrow::Cow::Borrowed(::type_path()), + received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())), + } + })?; + set.insert(value); + } + + Ok(bevy_platform::prelude::Box::new(set)) + } + + fn reflect_partial_eq(&self, value: &dyn $crate::reflect::PartialReflect) -> Option { + $crate::set::set_partial_eq(self, value) + } + } + + impl $crate::type_info::Typed for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn type_info() -> &'static $crate::type_info::TypeInfo { + static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + $crate::type_info::TypeInfo::Set( + $crate::set::SetInfo::new::().with_generics($crate::generics::Generics::from_iter([ + $crate::generics::TypeParamInfo::new::("V") + ])) + ) + }) + } + } + + impl $crate::type_registry::GetTypeRegistration for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync + Default, + { + fn get_type_registration() -> $crate::type_registry::TypeRegistration { + let mut registration = $crate::type_registry::TypeRegistration::of::(); + registration.insert::<$crate::type_registry::ReflectFromPtr>($crate::type_registry::FromType::::from_type()); + registration.insert::<$crate::from_reflect::ReflectFromReflect>($crate::type_registry::FromType::::from_type()); + registration + } + + fn register_type_dependencies(registry: &mut $crate::type_registry::TypeRegistry) { + registry.register::(); + } + } + + $crate::impl_full_reflect!( + for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + ); + + impl $crate::from_reflect::FromReflect for $ty + where + V: $crate::from_reflect::FromReflect + $crate::type_path::TypePath + $crate::type_registry::GetTypeRegistration + Eq + core::hash::Hash, + S: $crate::type_path::TypePath + core::hash::BuildHasher + Default + Send + Sync, + { + fn from_reflect(reflect: &dyn $crate::reflect::PartialReflect) -> Option { + let ref_set = reflect.reflect_ref().as_set().ok()?; + + let mut new_set = Self::with_capacity_and_hasher(ref_set.len(), S::default()); + + for value in ref_set.iter() { + let new_value = V::from_reflect(value)?; + new_set.insert(new_value); + } + + Some(new_set) + } + } + }; + }; +} + +pub(crate) use impl_reflect_for_hashset; diff --git a/crates/bevy_reflect/src/impls/petgraph.rs b/crates/bevy_reflect/src/impls/petgraph.rs index 1a1f3a34c4..ce2bf77e37 100644 --- a/crates/bevy_reflect/src/impls/petgraph.rs +++ b/crates/bevy_reflect/src/impls/petgraph.rs @@ -3,6 +3,8 @@ use crate::{impl_reflect_opaque, prelude::ReflectDefault, ReflectDeserialize, Re impl_reflect_opaque!(::petgraph::graph::NodeIndex( Clone, Default, + PartialEq, + Hash, Serialize, Deserialize )); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs deleted file mode 100644 index 54d8393e7a..0000000000 --- a/crates/bevy_reflect/src/impls/std.rs +++ /dev/null @@ -1,2843 +0,0 @@ -#![expect( - unused_qualifications, - reason = "Temporary workaround for impl_reflect!(Option/Result false-positive" -)] - -use crate::{ - impl_type_path, map_apply, map_partial_eq, map_try_apply, - prelude::ReflectDefault, - reflect::impl_full_reflect, - set_apply, set_partial_eq, set_try_apply, - utility::{reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell}, - ApplyError, Array, ArrayInfo, ArrayIter, DynamicMap, DynamicTypePath, FromReflect, FromType, - Generics, GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, MaybeTyped, - OpaqueInfo, PartialReflect, Reflect, ReflectCloneError, ReflectDeserialize, ReflectFromPtr, - ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, Set, - SetInfo, TypeInfo, TypeParamInfo, TypePath, TypeRegistration, TypeRegistry, Typed, -}; -use alloc::{ - borrow::{Cow, ToOwned}, - boxed::Box, - collections::VecDeque, - format, - string::ToString, - vec::Vec, -}; -use bevy_reflect_derive::{impl_reflect, impl_reflect_opaque}; -use core::{ - any::Any, - fmt, - hash::{BuildHasher, Hash, Hasher}, - panic::Location, -}; - -#[cfg(feature = "std")] -use std::path::Path; - -impl_reflect_opaque!(bool( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(char( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(u8( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(u16( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(u32( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(u64( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(u128( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(usize( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(i8( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(i16( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(i32( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(i64( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(i128( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(isize( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(f32( - Clone, - Debug, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(f64( - Clone, - Debug, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_type_path!(str); -impl_reflect_opaque!(::alloc::string::String( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -#[cfg(feature = "std")] -impl_reflect_opaque!(::std::path::PathBuf( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(::core::any::TypeId(Clone, Debug, Hash, PartialEq,)); -impl_reflect_opaque!(::alloc::collections::BTreeSet(Clone)); -impl_reflect_opaque!(::core::ops::Range(Clone)); -impl_reflect_opaque!(::core::ops::RangeInclusive(Clone)); -impl_reflect_opaque!(::core::ops::RangeFrom(Clone)); -impl_reflect_opaque!(::core::ops::RangeTo(Clone)); -impl_reflect_opaque!(::core::ops::RangeToInclusive(Clone)); -impl_reflect_opaque!(::core::ops::RangeFull(Clone)); -impl_reflect_opaque!(::core::ops::Bound(Clone)); -impl_reflect_opaque!(::core::time::Duration( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize, - Default -)); -impl_reflect_opaque!(::bevy_platform_support::time::Instant( - Clone, Debug, Hash, PartialEq -)); -impl_reflect_opaque!(::core::num::NonZeroI128( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroU128( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroIsize( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroUsize( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroI64( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroU64( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroU32( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroI32( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroI16( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroU16( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroU8( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::NonZeroI8( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -impl_reflect_opaque!(::core::num::Wrapping(Clone)); -impl_reflect_opaque!(::core::num::Saturating(Clone)); -impl_reflect_opaque!(::bevy_platform_support::sync::Arc(Clone)); - -// `Serialize` and `Deserialize` only for platforms supported by serde: -// https://github.com/serde-rs/serde/blob/3ffb86fc70efd3d329519e2dddfa306cc04f167c/serde/src/de/impls.rs#L1732 -#[cfg(all(any(unix, windows), feature = "std"))] -impl_reflect_opaque!(::std::ffi::OsString( - Clone, - Debug, - Hash, - PartialEq, - Serialize, - Deserialize -)); -#[cfg(all(not(any(unix, windows)), feature = "std"))] -impl_reflect_opaque!(::std::ffi::OsString(Clone, Debug, Hash, PartialEq)); -impl_reflect_opaque!(::alloc::collections::BinaryHeap(Clone)); - -macro_rules! impl_reflect_for_atomic { - ($ty:ty, $ordering:expr) => { - impl_type_path!($ty); - - const _: () = { - #[cfg(feature = "functions")] - crate::func::macros::impl_function_traits!($ty); - - impl GetTypeRegistration for $ty - where - $ty: Any + Send + Sync, - { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - - // Serde only supports atomic types when the "std" feature is enabled - #[cfg(feature = "std")] - { - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - } - - registration - } - } - - impl Typed for $ty - where - $ty: Any + Send + Sync, - { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| { - let info = OpaqueInfo::new::(); - TypeInfo::Opaque(info) - }) - } - } - - impl PartialReflect for $ty - where - $ty: Any + Send + Sync, - { - #[inline] - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - #[inline] - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - #[inline] - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - #[inline] - fn try_into_reflect( - self: Box, - ) -> Result, Box> { - Ok(self) - } - #[inline] - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - #[inline] - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - #[inline] - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(<$ty>::new(self.load($ordering)))) - } - - #[inline] - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - *self = <$ty>::new(value.load($ordering)); - } else { - return Err(ApplyError::MismatchedTypes { - from_type: Into::into(DynamicTypePath::reflect_type_path(value)), - to_type: Into::into(::type_path()), - }); - } - Ok(()) - } - #[inline] - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Opaque - } - #[inline] - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - #[inline] - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - #[inline] - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } - } - - impl FromReflect for $ty - where - $ty: Any + Send + Sync, - { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - Some(<$ty>::new( - reflect.try_downcast_ref::<$ty>()?.load($ordering), - )) - } - } - }; - - impl_full_reflect!(for $ty where $ty: Any + Send + Sync); - }; -} - -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicIsize, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicUsize, - ::core::sync::atomic::Ordering::SeqCst -); -#[cfg(target_has_atomic = "64")] -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicI64, - ::core::sync::atomic::Ordering::SeqCst -); -#[cfg(target_has_atomic = "64")] -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicU64, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicI32, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicU32, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicI16, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicU16, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicI8, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicU8, - ::core::sync::atomic::Ordering::SeqCst -); -impl_reflect_for_atomic!( - ::core::sync::atomic::AtomicBool, - ::core::sync::atomic::Ordering::SeqCst -); - -macro_rules! impl_reflect_for_veclike { - ($ty:ty, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { - impl List for $ty { - #[inline] - fn get(&self, index: usize) -> Option<&dyn PartialReflect> { - <$sub>::get(self, index).map(|value| value as &dyn PartialReflect) - } - - #[inline] - fn get_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { - <$sub>::get_mut(self, index).map(|value| value as &mut dyn PartialReflect) - } - - fn insert(&mut self, index: usize, value: Box) { - let value = value.try_take::().unwrap_or_else(|value| { - T::from_reflect(&*value).unwrap_or_else(|| { - panic!( - "Attempted to insert invalid value of type {}.", - value.reflect_type_path() - ) - }) - }); - $insert(self, index, value); - } - - fn remove(&mut self, index: usize) -> Box { - Box::new($remove(self, index)) - } - - fn push(&mut self, value: Box) { - let value = T::take_from_reflect(value).unwrap_or_else(|value| { - panic!( - "Attempted to push invalid value of type {}.", - value.reflect_type_path() - ) - }); - $push(self, value); - } - - fn pop(&mut self) -> Option> { - $pop(self).map(|value| Box::new(value) as Box) - } - - #[inline] - fn len(&self) -> usize { - <$sub>::len(self) - } - - #[inline] - fn iter(&self) -> ListIter { - ListIter::new(self) - } - - #[inline] - fn drain(&mut self) -> Vec> { - self.drain(..) - .map(|value| Box::new(value) as Box) - .collect() - } - } - - impl PartialReflect for $ty { - #[inline] - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - fn into_partial_reflect(self: Box) -> Box { - self - } - - #[inline] - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - #[inline] - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect( - self: Box, - ) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::List - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::List(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::List(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::List(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new( - self.iter() - .map(|value| { - value.reflect_clone()?.take().map_err(|_| { - ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(value.reflect_type_path().to_string()), - } - }) - }) - .collect::>()?, - )) - } - - fn reflect_hash(&self) -> Option { - crate::list_hash(self) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - crate::list_partial_eq(self, value) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - crate::list_apply(self, value); - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - crate::list_try_apply(self, value) - } - } - - impl_full_reflect!( for $ty where T: FromReflect + MaybeTyped + TypePath + GetTypeRegistration); - - impl Typed for $ty { - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| { - TypeInfo::List( - ListInfo::new::().with_generics(Generics::from_iter([ - TypeParamInfo::new::("T") - ])) - ) - }) - } - } - - impl GetTypeRegistration - for $ty - { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::<$ty>(); - registration.insert::(FromType::<$ty>::from_type()); - registration.insert::(FromType::<$ty>::from_type()); - registration - } - - fn register_type_dependencies(registry: &mut TypeRegistry) { - registry.register::(); - } - } - - impl FromReflect for $ty { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_list = reflect.reflect_ref().as_list().ok()?; - - let mut new_list = Self::with_capacity(ref_list.len()); - - for field in ref_list.iter() { - $push(&mut new_list, T::from_reflect(field)?); - } - - Some(new_list) - } - } - }; -} - -impl_reflect_for_veclike!(Vec, Vec::insert, Vec::remove, Vec::push, Vec::pop, [T]); -impl_type_path!(::alloc::vec::Vec); -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(Vec; ); - -impl_reflect_for_veclike!( - VecDeque, - VecDeque::insert, - VecDeque::remove, - VecDeque::push_back, - VecDeque::pop_back, - VecDeque:: -); -impl_type_path!(::alloc::collections::VecDeque); -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(VecDeque; ); - -macro_rules! impl_reflect_for_hashmap { - ($ty:path) => { - impl Map for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn get(&self, key: &dyn PartialReflect) -> Option<&dyn PartialReflect> { - key.try_downcast_ref::() - .and_then(|key| Self::get(self, key)) - .map(|value| value as &dyn PartialReflect) - } - - fn get_mut(&mut self, key: &dyn PartialReflect) -> Option<&mut dyn PartialReflect> { - key.try_downcast_ref::() - .and_then(move |key| Self::get_mut(self, key)) - .map(|value| value as &mut dyn PartialReflect) - } - - fn get_at(&self, index: usize) -> Option<(&dyn PartialReflect, &dyn PartialReflect)> { - self.iter() - .nth(index) - .map(|(key, value)| (key as &dyn PartialReflect, value as &dyn PartialReflect)) - } - - fn get_at_mut( - &mut self, - index: usize, - ) -> Option<(&dyn PartialReflect, &mut dyn PartialReflect)> { - self.iter_mut().nth(index).map(|(key, value)| { - (key as &dyn PartialReflect, value as &mut dyn PartialReflect) - }) - } - - fn len(&self) -> usize { - Self::len(self) - } - - fn iter(&self) -> MapIter { - MapIter::new(self) - } - - fn drain(&mut self) -> Vec<(Box, Box)> { - self.drain() - .map(|(key, value)| { - ( - Box::new(key) as Box, - Box::new(value) as Box, - ) - }) - .collect() - } - - fn to_dynamic_map(&self) -> DynamicMap { - let mut dynamic_map = DynamicMap::default(); - dynamic_map.set_represented_type(self.get_represented_type_info()); - for (k, v) in self { - let key = K::from_reflect(k).unwrap_or_else(|| { - panic!( - "Attempted to clone invalid key of type {}.", - k.reflect_type_path() - ) - }); - dynamic_map.insert_boxed(Box::new(key), v.to_dynamic()); - } - dynamic_map - } - - fn insert_boxed( - &mut self, - key: Box, - value: Box, - ) -> Option> { - let key = K::take_from_reflect(key).unwrap_or_else(|key| { - panic!( - "Attempted to insert invalid key of type {}.", - key.reflect_type_path() - ) - }); - let value = V::take_from_reflect(value).unwrap_or_else(|value| { - panic!( - "Attempted to insert invalid value of type {}.", - value.reflect_type_path() - ) - }); - self.insert(key, value) - .map(|old_value| Box::new(old_value) as Box) - } - - fn remove(&mut self, key: &dyn PartialReflect) -> Option> { - let mut from_reflect = None; - key.try_downcast_ref::() - .or_else(|| { - from_reflect = K::from_reflect(key); - from_reflect.as_ref() - }) - .and_then(|key| self.remove(key)) - .map(|value| Box::new(value) as Box) - } - } - - impl PartialReflect for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect( - self: Box, - ) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Map - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Map(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Map(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Map(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - let mut map = Self::with_capacity_and_hasher(self.len(), S::default()); - for (key, value) in self.iter() { - let key = key.reflect_clone()?.take().map_err(|_| { - ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(key.reflect_type_path().to_string()), - } - })?; - let value = value.reflect_clone()?.take().map_err(|_| { - ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(value.reflect_type_path().to_string()), - } - })?; - map.insert(key, value); - } - - Ok(Box::new(map)) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - map_partial_eq(self, value) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - map_apply(self, value); - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - map_try_apply(self, value) - } - } - - impl_full_reflect!( - for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync, - ); - - impl Typed for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| { - TypeInfo::Map( - MapInfo::new::().with_generics(Generics::from_iter([ - TypeParamInfo::new::("K"), - TypeParamInfo::new::("V"), - ])), - ) - }) - } - } - - impl GetTypeRegistration for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync + Default, - { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } - - fn register_type_dependencies(registry: &mut TypeRegistry) { - registry.register::(); - registry.register::(); - } - } - - impl FromReflect for $ty - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_map = reflect.reflect_ref().as_map().ok()?; - - let mut new_map = Self::with_capacity_and_hasher(ref_map.len(), S::default()); - - for (key, value) in ref_map.iter() { - let new_key = K::from_reflect(key)?; - let new_value = V::from_reflect(value)?; - new_map.insert(new_key, new_value); - } - - Some(new_map) - } - } - }; -} - -#[cfg(feature = "std")] -impl_reflect_for_hashmap!(::std::collections::HashMap); -impl_type_path!(::core::hash::BuildHasherDefault); -#[cfg(feature = "std")] -impl_type_path!(::std::collections::hash_map::RandomState); -#[cfg(feature = "std")] -impl_type_path!(::std::collections::HashMap); -#[cfg(all(feature = "functions", feature = "std"))] -crate::func::macros::impl_function_traits!(::std::collections::HashMap; - < - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync - > -); - -impl_reflect_for_hashmap!(bevy_platform_support::collections::HashMap); -impl_type_path!(::bevy_platform_support::collections::HashMap); -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(::bevy_platform_support::collections::HashMap; - < - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync - > -); - -macro_rules! impl_reflect_for_hashset { - ($ty:path) => { - impl Set for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn get(&self, value: &dyn PartialReflect) -> Option<&dyn PartialReflect> { - value - .try_downcast_ref::() - .and_then(|value| Self::get(self, value)) - .map(|value| value as &dyn PartialReflect) - } - - fn len(&self) -> usize { - Self::len(self) - } - - fn iter(&self) -> Box + '_> { - let iter = self.iter().map(|v| v as &dyn PartialReflect); - Box::new(iter) - } - - fn drain(&mut self) -> Vec> { - self.drain() - .map(|value| Box::new(value) as Box) - .collect() - } - - fn insert_boxed(&mut self, value: Box) -> bool { - let value = V::take_from_reflect(value).unwrap_or_else(|value| { - panic!( - "Attempted to insert invalid value of type {}.", - value.reflect_type_path() - ) - }); - self.insert(value) - } - - fn remove(&mut self, value: &dyn PartialReflect) -> bool { - let mut from_reflect = None; - value - .try_downcast_ref::() - .or_else(|| { - from_reflect = V::from_reflect(value); - from_reflect.as_ref() - }) - .is_some_and(|value| self.remove(value)) - } - - fn contains(&self, value: &dyn PartialReflect) -> bool { - let mut from_reflect = None; - value - .try_downcast_ref::() - .or_else(|| { - from_reflect = V::from_reflect(value); - from_reflect.as_ref() - }) - .is_some_and(|value| self.contains(value)) - } - } - - impl PartialReflect for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - #[inline] - fn try_into_reflect( - self: Box, - ) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - set_apply(self, value); - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - set_try_apply(self, value) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Set - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Set(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Set(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Set(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - let mut set = Self::with_capacity_and_hasher(self.len(), S::default()); - for value in self.iter() { - let value = value.reflect_clone()?.take().map_err(|_| { - ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(value.reflect_type_path().to_string()), - } - })?; - set.insert(value); - } - - Ok(Box::new(set)) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - set_partial_eq(self, value) - } - } - - impl Typed for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| { - TypeInfo::Set( - SetInfo::new::().with_generics(Generics::from_iter([ - TypeParamInfo::new::("V") - ])) - ) - }) - } - } - - impl GetTypeRegistration for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync + Default, - { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } - - fn register_type_dependencies(registry: &mut TypeRegistry) { - registry.register::(); - } - } - - impl_full_reflect!( - for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync, - ); - - impl FromReflect for $ty - where - V: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, - S: TypePath + BuildHasher + Default + Send + Sync, - { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_set = reflect.reflect_ref().as_set().ok()?; - - let mut new_set = Self::with_capacity_and_hasher(ref_set.len(), S::default()); - - for value in ref_set.iter() { - let new_value = V::from_reflect(value)?; - new_set.insert(new_value); - } - - Some(new_set) - } - } - }; -} - -impl_type_path!(::bevy_platform_support::hash::NoOpHash); -impl_type_path!(::bevy_platform_support::hash::FixedHasher); - -#[cfg(feature = "std")] -impl_reflect_for_hashset!(::std::collections::HashSet); -#[cfg(feature = "std")] -impl_type_path!(::std::collections::HashSet); -#[cfg(all(feature = "functions", feature = "std"))] -crate::func::macros::impl_function_traits!(::std::collections::HashSet; - < - V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync - > -); - -impl_reflect_for_hashset!(::bevy_platform_support::collections::HashSet); -impl_type_path!(::bevy_platform_support::collections::HashSet); -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(::bevy_platform_support::collections::HashSet; - < - V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, - S: TypePath + BuildHasher + Default + Send + Sync - > -); - -impl Map for ::alloc::collections::BTreeMap -where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -{ - fn get(&self, key: &dyn PartialReflect) -> Option<&dyn PartialReflect> { - key.try_downcast_ref::() - .and_then(|key| Self::get(self, key)) - .map(|value| value as &dyn PartialReflect) - } - - fn get_mut(&mut self, key: &dyn PartialReflect) -> Option<&mut dyn PartialReflect> { - key.try_downcast_ref::() - .and_then(move |key| Self::get_mut(self, key)) - .map(|value| value as &mut dyn PartialReflect) - } - - fn get_at(&self, index: usize) -> Option<(&dyn PartialReflect, &dyn PartialReflect)> { - self.iter() - .nth(index) - .map(|(key, value)| (key as &dyn PartialReflect, value as &dyn PartialReflect)) - } - - fn get_at_mut( - &mut self, - index: usize, - ) -> Option<(&dyn PartialReflect, &mut dyn PartialReflect)> { - self.iter_mut() - .nth(index) - .map(|(key, value)| (key as &dyn PartialReflect, value as &mut dyn PartialReflect)) - } - - fn len(&self) -> usize { - Self::len(self) - } - - fn iter(&self) -> MapIter { - MapIter::new(self) - } - - fn drain(&mut self) -> Vec<(Box, Box)> { - // BTreeMap doesn't have a `drain` function. See - // https://github.com/rust-lang/rust/issues/81074. So we have to fake one by popping - // elements off one at a time. - let mut result = Vec::with_capacity(self.len()); - while let Some((k, v)) = self.pop_first() { - result.push(( - Box::new(k) as Box, - Box::new(v) as Box, - )); - } - result - } - - fn clone_dynamic(&self) -> DynamicMap { - let mut dynamic_map = DynamicMap::default(); - dynamic_map.set_represented_type(self.get_represented_type_info()); - for (k, v) in self { - let key = K::from_reflect(k).unwrap_or_else(|| { - panic!( - "Attempted to clone invalid key of type {}.", - k.reflect_type_path() - ) - }); - dynamic_map.insert_boxed(Box::new(key), v.to_dynamic()); - } - dynamic_map - } - - fn insert_boxed( - &mut self, - key: Box, - value: Box, - ) -> Option> { - let key = K::take_from_reflect(key).unwrap_or_else(|key| { - panic!( - "Attempted to insert invalid key of type {}.", - key.reflect_type_path() - ) - }); - let value = V::take_from_reflect(value).unwrap_or_else(|value| { - panic!( - "Attempted to insert invalid value of type {}.", - value.reflect_type_path() - ) - }); - self.insert(key, value) - .map(|old_value| Box::new(old_value) as Box) - } - - fn remove(&mut self, key: &dyn PartialReflect) -> Option> { - let mut from_reflect = None; - key.try_downcast_ref::() - .or_else(|| { - from_reflect = K::from_reflect(key); - from_reflect.as_ref() - }) - .and_then(|key| self.remove(key)) - .map(|value| Box::new(value) as Box) - } -} - -impl PartialReflect for ::alloc::collections::BTreeMap -where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -{ - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - #[inline] - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Map - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Map(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Map(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Map(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - let mut map = Self::new(); - for (key, value) in self.iter() { - let key = - key.reflect_clone()? - .take() - .map_err(|_| ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(key.reflect_type_path().to_string()), - })?; - let value = - value - .reflect_clone()? - .take() - .map_err(|_| ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(value.reflect_type_path().to_string()), - })?; - map.insert(key, value); - } - - Ok(Box::new(map)) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - map_partial_eq(self, value) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - map_apply(self, value); - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - map_try_apply(self, value) - } -} - -impl_full_reflect!( - for ::alloc::collections::BTreeMap - where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -); - -impl Typed for ::alloc::collections::BTreeMap -where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -{ - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| { - TypeInfo::Map( - MapInfo::new::().with_generics(Generics::from_iter([ - TypeParamInfo::new::("K"), - TypeParamInfo::new::("V"), - ])), - ) - }) - } -} - -impl GetTypeRegistration for ::alloc::collections::BTreeMap -where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -{ - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } -} - -impl FromReflect for ::alloc::collections::BTreeMap -where - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, -{ - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_map = reflect.reflect_ref().as_map().ok()?; - - let mut new_map = Self::new(); - - for (key, value) in ref_map.iter() { - let new_key = K::from_reflect(key)?; - let new_value = V::from_reflect(value)?; - new_map.insert(new_key, new_value); - } - - Some(new_map) - } -} - -impl_type_path!(::alloc::collections::BTreeMap); -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(::alloc::collections::BTreeMap; - < - K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Ord, - V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration - > -); - -impl Array for [T; N] { - #[inline] - fn get(&self, index: usize) -> Option<&dyn PartialReflect> { - <[T]>::get(self, index).map(|value| value as &dyn PartialReflect) - } - - #[inline] - fn get_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { - <[T]>::get_mut(self, index).map(|value| value as &mut dyn PartialReflect) - } - - #[inline] - fn len(&self) -> usize { - N - } - - #[inline] - fn iter(&self) -> ArrayIter { - ArrayIter::new(self) - } - - #[inline] - fn drain(self: Box) -> Vec> { - self.into_iter() - .map(|value| Box::new(value) as Box) - .collect() - } -} - -impl PartialReflect - for [T; N] -{ - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - #[inline] - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Array - } - - #[inline] - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Array(self) - } - - #[inline] - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Array(self) - } - - #[inline] - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Array(self) - } - - #[inline] - fn reflect_hash(&self) -> Option { - crate::array_hash(self) - } - - #[inline] - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - crate::array_partial_eq(self, value) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - crate::array_apply(self, value); - } - - #[inline] - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - crate::array_try_apply(self, value) - } -} - -impl Reflect for [T; N] { - #[inline] - fn into_any(self: Box) -> Box { - self - } - - #[inline] - fn as_any(&self) -> &dyn Any { - self - } - - #[inline] - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - #[inline] - fn into_reflect(self: Box) -> Box { - self - } - - #[inline] - fn as_reflect(&self) -> &dyn Reflect { - self - } - - #[inline] - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - #[inline] - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -impl FromReflect - for [T; N] -{ - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_array = reflect.reflect_ref().as_array().ok()?; - - let mut temp_vec = Vec::with_capacity(ref_array.len()); - - for field in ref_array.iter() { - temp_vec.push(T::from_reflect(field)?); - } - - temp_vec.try_into().ok() - } -} - -impl Typed for [T; N] { - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| TypeInfo::Array(ArrayInfo::new::(N))) - } -} - -impl TypePath for [T; N] { - fn type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("[{t}; {N}]", t = T::type_path())) - } - - fn short_type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("[{t}; {N}]", t = T::short_type_path())) - } -} - -impl GetTypeRegistration - for [T; N] -{ - fn get_type_registration() -> TypeRegistration { - TypeRegistration::of::<[T; N]>() - } - - fn register_type_dependencies(registry: &mut TypeRegistry) { - registry.register::(); - } -} - -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!([T; N]; [const N: usize]); - -impl_reflect! { - #[type_path = "core::option"] - enum Option { - None, - Some(T), - } -} - -impl_reflect! { - #[type_path = "core::result"] - enum Result { - Ok(T), - Err(E), - } -} - -impl TypePath for &'static T { - fn type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("&{}", T::type_path())) - } - - fn short_type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("&{}", T::short_type_path())) - } -} - -impl TypePath for &'static mut T { - fn type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("&mut {}", T::type_path())) - } - - fn short_type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("&mut {}", T::short_type_path())) - } -} - -impl PartialReflect for Cow<'static, str> { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Opaque - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(self.clone())) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - Hash::hash(&Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - if let Some(value) = value.try_downcast_ref::() { - Some(PartialEq::eq(self, value)) - } else { - Some(false) - } - } - - fn debug(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result { - fmt::Debug::fmt(self, f) - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - self.clone_from(value); - } else { - return Err(ApplyError::MismatchedTypes { - from_type: value.reflect_type_path().into(), - // If we invoke the reflect_type_path on self directly the borrow checker complains that the lifetime of self must outlive 'static - to_type: Self::type_path().into(), - }); - } - Ok(()) - } -} - -impl_full_reflect!(for Cow<'static, str>); - -impl Typed for Cow<'static, str> { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) - } -} - -impl GetTypeRegistration for Cow<'static, str> { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::>(); - registration.insert::(FromType::>::from_type()); - registration.insert::(FromType::>::from_type()); - registration.insert::(FromType::>::from_type()); - registration.insert::(FromType::>::from_type()); - registration - } -} - -impl FromReflect for Cow<'static, str> { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - Some(reflect.try_downcast_ref::>()?.clone()) - } -} - -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(Cow<'static, str>); - -impl TypePath for [T] -where - [T]: ToOwned, -{ - fn type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("[{}]", ::type_path())) - } - - fn short_type_path() -> &'static str { - static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| format!("[{}]", ::short_type_path())) - } -} - -impl List - for Cow<'static, [T]> -{ - fn get(&self, index: usize) -> Option<&dyn PartialReflect> { - self.as_ref().get(index).map(|x| x as &dyn PartialReflect) - } - - fn get_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> { - self.to_mut() - .get_mut(index) - .map(|x| x as &mut dyn PartialReflect) - } - - fn insert(&mut self, index: usize, element: Box) { - let value = T::take_from_reflect(element).unwrap_or_else(|value| { - panic!( - "Attempted to insert invalid value of type {}.", - value.reflect_type_path() - ); - }); - self.to_mut().insert(index, value); - } - - fn remove(&mut self, index: usize) -> Box { - Box::new(self.to_mut().remove(index)) - } - - fn push(&mut self, value: Box) { - let value = T::take_from_reflect(value).unwrap_or_else(|value| { - panic!( - "Attempted to push invalid value of type {}.", - value.reflect_type_path() - ) - }); - self.to_mut().push(value); - } - - fn pop(&mut self) -> Option> { - self.to_mut() - .pop() - .map(|value| Box::new(value) as Box) - } - - fn len(&self) -> usize { - self.as_ref().len() - } - - fn iter(&self) -> ListIter { - ListIter::new(self) - } - - fn drain(&mut self) -> Vec> { - self.to_mut() - .drain(..) - .map(|value| Box::new(value) as Box) - .collect() - } -} - -impl PartialReflect - for Cow<'static, [T]> -{ - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::List - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::List(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::List(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::List(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(self.clone())) - } - - fn reflect_hash(&self) -> Option { - crate::list_hash(self) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - crate::list_partial_eq(self, value) - } - - fn apply(&mut self, value: &dyn PartialReflect) { - crate::list_apply(self, value); - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - crate::list_try_apply(self, value) - } -} - -impl_full_reflect!( - for Cow<'static, [T]> - where - T: FromReflect + Clone + MaybeTyped + TypePath + GetTypeRegistration, -); - -impl Typed - for Cow<'static, [T]> -{ - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) - } -} - -impl GetTypeRegistration - for Cow<'static, [T]> -{ - fn get_type_registration() -> TypeRegistration { - TypeRegistration::of::>() - } - - fn register_type_dependencies(registry: &mut TypeRegistry) { - registry.register::(); - } -} - -impl FromReflect - for Cow<'static, [T]> -{ - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - let ref_list = reflect.reflect_ref().as_list().ok()?; - - let mut temp_vec = Vec::with_capacity(ref_list.len()); - - for field in ref_list.iter() { - temp_vec.push(T::from_reflect(field)?); - } - - Some(temp_vec.into()) - } -} - -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(Cow<'static, [T]>; ); - -impl PartialReflect for &'static str { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(*self)) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - Hash::hash(&Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - if let Some(value) = value.try_downcast_ref::() { - Some(PartialEq::eq(self, value)) - } else { - Some(false) - } - } - - fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - self.clone_from(value); - } else { - return Err(ApplyError::MismatchedTypes { - from_type: value.reflect_type_path().into(), - to_type: Self::type_path().into(), - }); - } - Ok(()) - } -} - -impl Reflect for &'static str { - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -impl Typed for &'static str { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) - } -} - -impl GetTypeRegistration for &'static str { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } -} - -impl FromReflect for &'static str { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - reflect.try_downcast_ref::().copied() - } -} - -#[cfg(feature = "functions")] -crate::func::macros::impl_function_traits!(&'static str); - -#[cfg(feature = "std")] -impl PartialReflect for &'static Path { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Opaque - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(*self)) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - Hash::hash(&Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - if let Some(value) = value.try_downcast_ref::() { - Some(PartialEq::eq(self, value)) - } else { - Some(false) - } - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - self.clone_from(value); - Ok(()) - } else { - Err(ApplyError::MismatchedTypes { - from_type: value.reflect_type_path().into(), - to_type: ::reflect_type_path(self).into(), - }) - } - } -} - -#[cfg(feature = "std")] -impl Reflect for &'static Path { - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -#[cfg(feature = "std")] -impl Typed for &'static Path { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) - } -} - -#[cfg(feature = "std")] -impl GetTypeRegistration for &'static Path { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } -} - -#[cfg(feature = "std")] -impl FromReflect for &'static Path { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - reflect.try_downcast_ref::().copied() - } -} - -#[cfg(all(feature = "functions", feature = "std"))] -crate::func::macros::impl_function_traits!(&'static Path); - -#[cfg(feature = "std")] -impl PartialReflect for Cow<'static, Path> { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Opaque - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(self.clone())) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - Hash::hash(&Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - if let Some(value) = value.try_downcast_ref::() { - Some(PartialEq::eq(self, value)) - } else { - Some(false) - } - } - - fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self, f) - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - self.clone_from(value); - Ok(()) - } else { - Err(ApplyError::MismatchedTypes { - from_type: value.reflect_type_path().into(), - to_type: ::reflect_type_path(self).into(), - }) - } - } -} - -#[cfg(feature = "std")] -impl Reflect for Cow<'static, Path> { - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -#[cfg(feature = "std")] -impl Typed for Cow<'static, Path> { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) - } -} - -#[cfg(feature = "std")] -impl_type_path!(::std::path::Path); -impl_type_path!(::alloc::borrow::Cow<'a: 'static, T: ToOwned + ?Sized>); - -#[cfg(feature = "std")] -impl FromReflect for Cow<'static, Path> { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - Some(reflect.try_downcast_ref::()?.clone()) - } -} - -#[cfg(feature = "std")] -impl GetTypeRegistration for Cow<'static, Path> { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } -} - -#[cfg(all(feature = "functions", feature = "std"))] -crate::func::macros::impl_function_traits!(Cow<'static, Path>); - -impl TypePath for &'static Location<'static> { - fn type_path() -> &'static str { - "core::panic::Location" - } - - fn short_type_path() -> &'static str { - "Location" - } -} - -impl PartialReflect for &'static Location<'static> { - fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { - Some(::type_info()) - } - - #[inline] - fn into_partial_reflect(self: Box) -> Box { - self - } - - fn as_partial_reflect(&self) -> &dyn PartialReflect { - self - } - - fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { - self - } - - fn try_into_reflect(self: Box) -> Result, Box> { - Ok(self) - } - - fn try_as_reflect(&self) -> Option<&dyn Reflect> { - Some(self) - } - - fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { - Some(self) - } - - fn reflect_kind(&self) -> ReflectKind { - ReflectKind::Opaque - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Opaque(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Opaque(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Opaque(self) - } - - fn reflect_clone(&self) -> Result, ReflectCloneError> { - Ok(Box::new(*self)) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = reflect_hasher(); - Hash::hash(&Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { - if let Some(value) = value.try_downcast_ref::() { - Some(PartialEq::eq(self, value)) - } else { - Some(false) - } - } - - fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { - if let Some(value) = value.try_downcast_ref::() { - self.clone_from(value); - Ok(()) - } else { - Err(ApplyError::MismatchedTypes { - from_type: value.reflect_type_path().into(), - to_type: ::reflect_type_path(self).into(), - }) - } - } -} - -impl Reflect for &'static Location<'static> { - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } -} - -impl Typed for &'static Location<'static> { - fn type_info() -> &'static TypeInfo { - static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); - CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) - } -} - -impl GetTypeRegistration for &'static Location<'static> { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::(); - registration.insert::(FromType::::from_type()); - registration.insert::(FromType::::from_type()); - registration - } -} - -impl FromReflect for &'static Location<'static> { - fn from_reflect(reflect: &dyn PartialReflect) -> Option { - reflect.try_downcast_ref::().copied() - } -} - -#[cfg(all(feature = "functions", feature = "std"))] -crate::func::macros::impl_function_traits!(&'static Location<'static>); - -#[cfg(test)] -mod tests { - use crate::{ - Enum, FromReflect, PartialReflect, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, - Typed, VariantInfo, VariantType, - }; - use alloc::{collections::BTreeMap, string::String, vec}; - use bevy_platform_support::collections::HashMap; - use bevy_platform_support::time::Instant; - use core::{ - f32::consts::{PI, TAU}, - time::Duration, - }; - use static_assertions::assert_impl_all; - use std::path::Path; - - #[test] - fn can_serialize_duration() { - let mut type_registry = TypeRegistry::default(); - type_registry.register::(); - - let reflect_serialize = type_registry - .get_type_data::(core::any::TypeId::of::()) - .unwrap(); - let _serializable = reflect_serialize.get_serializable(&Duration::ZERO); - } - - #[test] - fn should_partial_eq_char() { - let a: &dyn PartialReflect = &'x'; - let b: &dyn PartialReflect = &'x'; - let c: &dyn PartialReflect = &'o'; - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_i32() { - let a: &dyn PartialReflect = &123_i32; - let b: &dyn PartialReflect = &123_i32; - let c: &dyn PartialReflect = &321_i32; - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_f32() { - let a: &dyn PartialReflect = &PI; - let b: &dyn PartialReflect = &PI; - let c: &dyn PartialReflect = &TAU; - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_string() { - let a: &dyn PartialReflect = &String::from("Hello"); - let b: &dyn PartialReflect = &String::from("Hello"); - let c: &dyn PartialReflect = &String::from("World"); - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_vec() { - let a: &dyn PartialReflect = &vec![1, 2, 3]; - let b: &dyn PartialReflect = &vec![1, 2, 3]; - let c: &dyn PartialReflect = &vec![3, 2, 1]; - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_hash_map() { - let mut a = >::default(); - a.insert(0usize, 1.23_f64); - let b = a.clone(); - let mut c = >::default(); - c.insert(0usize, 3.21_f64); - - let a: &dyn PartialReflect = &a; - let b: &dyn PartialReflect = &b; - let c: &dyn PartialReflect = &c; - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - assert!(!a.reflect_partial_eq(c).unwrap_or_default()); - } - - #[test] - fn should_partial_eq_btree_map() { - let mut a = BTreeMap::new(); - a.insert(0usize, 1.23_f64); - let b = a.clone(); - let mut c = BTreeMap::new(); - c.insert(0usize, 3.21_f64); - - let a: &dyn Reflect = &a; - let b: &dyn Reflect = &b; - let c: &dyn Reflect = &c; - assert!(a - .reflect_partial_eq(b.as_partial_reflect()) - .unwrap_or_default()); - assert!(!a - .reflect_partial_eq(c.as_partial_reflect()) - .unwrap_or_default()); - } - - #[test] - fn should_partial_eq_option() { - let a: &dyn PartialReflect = &Some(123); - let b: &dyn PartialReflect = &Some(123); - assert_eq!(Some(true), a.reflect_partial_eq(b)); - } - - #[test] - fn option_should_impl_enum() { - assert_impl_all!(Option<()>: Enum); - - let mut value = Some(123usize); - - assert!(value - .reflect_partial_eq(&Some(123usize)) - .unwrap_or_default()); - assert!(!value - .reflect_partial_eq(&Some(321usize)) - .unwrap_or_default()); - - assert_eq!("Some", value.variant_name()); - assert_eq!("core::option::Option::Some", value.variant_path()); - - if value.is_variant(VariantType::Tuple) { - if let Some(field) = value - .field_at_mut(0) - .and_then(|field| field.try_downcast_mut::()) - { - *field = 321; - } - } else { - panic!("expected `VariantType::Tuple`"); - } - - assert_eq!(Some(321), value); - } - - #[test] - fn option_should_from_reflect() { - #[derive(Reflect, PartialEq, Debug)] - struct Foo(usize); - - let expected = Some(Foo(123)); - let output = as FromReflect>::from_reflect(&expected).unwrap(); - - assert_eq!(expected, output); - } - - #[test] - fn option_should_apply() { - #[derive(Reflect, PartialEq, Debug)] - struct Foo(usize); - - // === None on None === // - let patch = None::; - let mut value = None::; - PartialReflect::apply(&mut value, &patch); - - assert_eq!(patch, value, "None apply onto None"); - - // === Some on None === // - let patch = Some(Foo(123)); - let mut value = None::; - PartialReflect::apply(&mut value, &patch); - - assert_eq!(patch, value, "Some apply onto None"); - - // === None on Some === // - let patch = None::; - let mut value = Some(Foo(321)); - PartialReflect::apply(&mut value, &patch); - - assert_eq!(patch, value, "None apply onto Some"); - - // === Some on Some === // - let patch = Some(Foo(123)); - let mut value = Some(Foo(321)); - PartialReflect::apply(&mut value, &patch); - - assert_eq!(patch, value, "Some apply onto Some"); - } - - #[test] - fn option_should_impl_typed() { - assert_impl_all!(Option<()>: Typed); - - type MyOption = Option; - let info = MyOption::type_info(); - if let TypeInfo::Enum(info) = info { - assert_eq!( - "None", - info.variant_at(0).unwrap().name(), - "Expected `None` to be variant at index `0`" - ); - assert_eq!( - "Some", - info.variant_at(1).unwrap().name(), - "Expected `Some` to be variant at index `1`" - ); - assert_eq!("Some", info.variant("Some").unwrap().name()); - if let VariantInfo::Tuple(variant) = info.variant("Some").unwrap() { - assert!( - variant.field_at(0).unwrap().is::(), - "Expected `Some` variant to contain `i32`" - ); - assert!( - variant.field_at(1).is_none(), - "Expected `Some` variant to only contain 1 field" - ); - } else { - panic!("Expected `VariantInfo::Tuple`"); - } - } else { - panic!("Expected `TypeInfo::Enum`"); - } - } - - #[test] - fn nonzero_usize_impl_reflect_from_reflect() { - let a: &dyn PartialReflect = &core::num::NonZero::::new(42).unwrap(); - let b: &dyn PartialReflect = &core::num::NonZero::::new(42).unwrap(); - assert!(a.reflect_partial_eq(b).unwrap_or_default()); - let forty_two: core::num::NonZero = FromReflect::from_reflect(a).unwrap(); - assert_eq!(forty_two, core::num::NonZero::::new(42).unwrap()); - } - - #[test] - fn instant_should_from_reflect() { - let expected = Instant::now(); - let output = ::from_reflect(&expected).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn path_should_from_reflect() { - let path = Path::new("hello_world.rs"); - let output = <&'static Path as FromReflect>::from_reflect(&path).unwrap(); - assert_eq!(path, output); - } - - #[test] - fn type_id_should_from_reflect() { - let type_id = core::any::TypeId::of::(); - let output = ::from_reflect(&type_id).unwrap(); - assert_eq!(type_id, output); - } - - #[test] - fn static_str_should_from_reflect() { - let expected = "Hello, World!"; - let output = <&'static str as FromReflect>::from_reflect(&expected).unwrap(); - assert_eq!(expected, output); - } -} diff --git a/crates/bevy_reflect/src/impls/std/collections/hash_map.rs b/crates/bevy_reflect/src/impls/std/collections/hash_map.rs new file mode 100644 index 0000000000..604c29f517 --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/collections/hash_map.rs @@ -0,0 +1,34 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_hashmap; +#[cfg(feature = "functions")] +use crate::{ + from_reflect::FromReflect, type_info::MaybeTyped, type_path::TypePath, + type_registry::GetTypeRegistration, +}; +#[cfg(feature = "functions")] +use core::hash::{BuildHasher, Hash}; + +impl_reflect_for_hashmap!(::std::collections::HashMap); +impl_type_path!(::std::collections::hash_map::RandomState); +impl_type_path!(::std::collections::HashMap); + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::std::collections::HashMap; + < + K: FromReflect + MaybeTyped + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + MaybeTyped + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); + +#[cfg(test)] +mod tests { + use crate::Reflect; + use static_assertions::assert_impl_all; + + #[test] + fn should_reflect_hashmaps() { + assert_impl_all!(std::collections::HashMap: Reflect); + } +} diff --git a/crates/bevy_reflect/src/impls/std/collections/hash_set.rs b/crates/bevy_reflect/src/impls/std/collections/hash_set.rs new file mode 100644 index 0000000000..26d2dd47ca --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/collections/hash_set.rs @@ -0,0 +1,17 @@ +use bevy_reflect_derive::impl_type_path; + +use crate::impls::macros::impl_reflect_for_hashset; +#[cfg(feature = "functions")] +use crate::{from_reflect::FromReflect, type_path::TypePath, type_registry::GetTypeRegistration}; +#[cfg(feature = "functions")] +use core::hash::{BuildHasher, Hash}; + +impl_reflect_for_hashset!(::std::collections::HashSet); +impl_type_path!(::std::collections::HashSet); +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(::std::collections::HashSet; + < + V: Hash + Eq + FromReflect + TypePath + GetTypeRegistration, + S: TypePath + BuildHasher + Default + Send + Sync + > +); diff --git a/crates/bevy_reflect/src/impls/std/collections/mod.rs b/crates/bevy_reflect/src/impls/std/collections/mod.rs new file mode 100644 index 0000000000..2bde6a0653 --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/collections/mod.rs @@ -0,0 +1,2 @@ +mod hash_map; +mod hash_set; diff --git a/crates/bevy_reflect/src/impls/std/ffi.rs b/crates/bevy_reflect/src/impls/std/ffi.rs new file mode 100644 index 0000000000..92cacc3b66 --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/ffi.rs @@ -0,0 +1,18 @@ +#[cfg(any(unix, windows))] +use crate::type_registry::{ReflectDeserialize, ReflectSerialize}; +use bevy_reflect_derive::impl_reflect_opaque; + +// `Serialize` and `Deserialize` only for platforms supported by serde: +// https://github.com/serde-rs/serde/blob/3ffb86fc70efd3d329519e2dddfa306cc04f167c/serde/src/de/impls.rs#L1732 +#[cfg(any(unix, windows))] +impl_reflect_opaque!(::std::ffi::OsString( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize +)); + +#[cfg(not(any(unix, windows)))] +impl_reflect_opaque!(::std::ffi::OsString(Clone, Debug, Hash, PartialEq)); diff --git a/crates/bevy_reflect/src/impls/std/mod.rs b/crates/bevy_reflect/src/impls/std/mod.rs new file mode 100644 index 0000000000..b3586e4d7d --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/mod.rs @@ -0,0 +1,46 @@ +mod collections; +mod ffi; +mod path; + +#[cfg(test)] +mod tests { + use crate::{FromReflect, PartialReflect}; + use std::collections::HashMap; + use std::path::Path; + + #[test] + fn should_partial_eq_hash_map() { + let mut a = >::default(); + a.insert(0usize, 1.23_f64); + let b = a.clone(); + let mut c = >::default(); + c.insert(0usize, 3.21_f64); + + let a: &dyn PartialReflect = &a; + let b: &dyn PartialReflect = &b; + let c: &dyn PartialReflect = &c; + assert!(a.reflect_partial_eq(b).unwrap_or_default()); + assert!(!a.reflect_partial_eq(c).unwrap_or_default()); + } + + #[test] + fn path_should_from_reflect() { + let path = Path::new("hello_world.rs"); + let output = <&'static Path as FromReflect>::from_reflect(&path).unwrap(); + assert_eq!(path, output); + } + + #[test] + fn type_id_should_from_reflect() { + let type_id = core::any::TypeId::of::(); + let output = ::from_reflect(&type_id).unwrap(); + assert_eq!(type_id, output); + } + + #[test] + fn static_str_should_from_reflect() { + let expected = "Hello, World!"; + let output = <&'static str as FromReflect>::from_reflect(&expected).unwrap(); + assert_eq!(expected, output); + } +} diff --git a/crates/bevy_reflect/src/impls/std/path.rs b/crates/bevy_reflect/src/impls/std/path.rs new file mode 100644 index 0000000000..a73ee44141 --- /dev/null +++ b/crates/bevy_reflect/src/impls/std/path.rs @@ -0,0 +1,306 @@ +use crate::{ + error::ReflectCloneError, + kind::{ReflectKind, ReflectMut, ReflectOwned, ReflectRef}, + prelude::*, + reflect::ApplyError, + type_info::{OpaqueInfo, TypeInfo, Typed}, + type_path::DynamicTypePath, + type_registry::{ + FromType, GetTypeRegistration, ReflectDeserialize, ReflectFromPtr, ReflectSerialize, + TypeRegistration, + }, + utility::{reflect_hasher, NonGenericTypeInfoCell}, +}; +use alloc::borrow::Cow; +use bevy_platform::prelude::*; +use bevy_reflect_derive::{impl_reflect_opaque, impl_type_path}; +use core::any::Any; +use core::fmt; +use core::hash::{Hash, Hasher}; +use std::path::Path; + +impl_reflect_opaque!(::std::path::PathBuf( + Clone, + Debug, + Hash, + PartialEq, + Serialize, + Deserialize, + Default +)); + +impl PartialReflect for &'static Path { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(*self)) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + Ok(()) + } else { + Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: ::reflect_type_path(self).into(), + }) + } + } +} + +impl Reflect for &'static Path { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl Typed for &'static Path { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl GetTypeRegistration for &'static Path { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for &'static Path { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + reflect.try_downcast_ref::().copied() + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(&'static Path); + +impl PartialReflect for Cow<'static, Path> { + fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { + Some(::type_info()) + } + + #[inline] + fn into_partial_reflect(self: Box) -> Box { + self + } + + fn as_partial_reflect(&self) -> &dyn PartialReflect { + self + } + + fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect { + self + } + + fn try_into_reflect(self: Box) -> Result, Box> { + Ok(self) + } + + fn try_as_reflect(&self) -> Option<&dyn Reflect> { + Some(self) + } + + fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> { + Some(self) + } + + fn reflect_kind(&self) -> ReflectKind { + ReflectKind::Opaque + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Opaque(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Opaque(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Opaque(self) + } + + fn reflect_clone(&self) -> Result, ReflectCloneError> { + Ok(Box::new(self.clone())) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = reflect_hasher(); + Hash::hash(&Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn PartialReflect) -> Option { + if let Some(value) = value.try_downcast_ref::() { + Some(PartialEq::eq(self, value)) + } else { + Some(false) + } + } + + fn debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self, f) + } + + fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> { + if let Some(value) = value.try_downcast_ref::() { + self.clone_from(value); + Ok(()) + } else { + Err(ApplyError::MismatchedTypes { + from_type: value.reflect_type_path().into(), + to_type: ::reflect_type_path(self).into(), + }) + } + } +} + +impl Reflect for Cow<'static, Path> { + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } +} + +impl Typed for Cow<'static, Path> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Opaque(OpaqueInfo::new::())) + } +} + +impl_type_path!(::std::path::Path); + +impl FromReflect for Cow<'static, Path> { + fn from_reflect(reflect: &dyn PartialReflect) -> Option { + Some(reflect.try_downcast_ref::()?.clone()) + } +} + +impl GetTypeRegistration for Cow<'static, Path> { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + +#[cfg(feature = "functions")] +crate::func::macros::impl_function_traits!(Cow<'static, Path>); diff --git a/crates/bevy_reflect/src/impls/uuid.rs b/crates/bevy_reflect/src/impls/uuid.rs index 7385304e28..502b64c898 100644 --- a/crates/bevy_reflect/src/impls/uuid.rs +++ b/crates/bevy_reflect/src/impls/uuid.rs @@ -10,3 +10,12 @@ impl_reflect_opaque!(::uuid::Uuid( PartialEq, Hash )); + +impl_reflect_opaque!(::uuid::NonNilUuid( + Serialize, + Deserialize, + Clone, + Debug, + PartialEq, + Hash +)); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 64d07513ea..ab2fcc6b15 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -589,7 +589,14 @@ mod type_path; mod type_registry; mod impls { + mod alloc; + mod bevy_platform; + mod core; mod foldhash; + #[cfg(feature = "hashbrown")] + mod hashbrown; + mod macros; + #[cfg(feature = "std")] mod std; #[cfg(feature = "glam")] @@ -734,7 +741,7 @@ mod tests { vec, vec::Vec, }; - use bevy_platform_support::collections::HashMap; + use bevy_platform::collections::HashMap; use core::{ any::TypeId, fmt::{Debug, Formatter}, @@ -988,6 +995,41 @@ mod tests { assert_eq!(values, vec![1]); } + /// This test ensures that we are able to reflect generic types with one or more type parameters. + /// + /// When there is an `Add` implementation for `String`, the compiler isn't able to infer the correct + /// type to deref to. + /// If we don't append the strings in the `TypePath` derive correctly (i.e. explicitly specifying the type), + /// we'll get a compilation error saying that "`&String` cannot be added to `String`". + /// + /// So this test just ensures that we do do that correctly. + /// + /// This problem is a known issue and is unexpectedly expected behavior: + /// - + /// - + /// - + #[test] + fn should_reflect_generic() { + struct FakeString {} + + // This implementation confuses the compiler when trying to add a `&String` to a `String` + impl core::ops::Add for String { + type Output = Self; + fn add(self, _rhs: FakeString) -> Self::Output { + unreachable!() + } + } + + #[derive(Reflect)] + struct Foo(A); + + #[derive(Reflect)] + struct Bar(A, B); + + #[derive(Reflect)] + struct Baz(A, B, C); + } + #[test] fn should_reflect_clone() { // Struct @@ -2566,7 +2608,7 @@ bevy_reflect::tests::Test { let foo = Foo { a: 1 }; let foo: &dyn Reflect = &foo; - assert_eq!("123", format!("{:?}", foo)); + assert_eq!("123", format!("{foo:?}")); } #[test] @@ -2819,7 +2861,7 @@ bevy_reflect::tests::Test { test_unknown_tuple_struct.insert(14); test_struct.insert("unknown_tuplestruct", test_unknown_tuple_struct); assert_eq!( - format!("{:?}", test_struct), + format!("{test_struct:?}"), "DynamicStruct(bevy_reflect::tests::TestStruct { \ tuple: DynamicTuple((0, 1)), \ tuple_struct: DynamicTupleStruct(bevy_reflect::tests::TestTupleStruct(8)), \ diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 2e1c085676..4ecdb63275 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -103,12 +103,6 @@ pub trait List: PartialReflect { /// [`Vec`] will match the order of items in `self`. fn drain(&mut self) -> Vec>; - /// Clones the list, producing a [`DynamicList`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_list` instead")] - fn clone_dynamic(&self) -> DynamicList { - self.to_dynamic_list() - } - /// Creates a new [`DynamicList`] from this list. fn to_dynamic_list(&self) -> DynamicList { DynamicList { @@ -197,8 +191,7 @@ impl DynamicList { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::List(_)), - "expected TypeInfo::List but received: {:?}", - represented_type + "expected TypeInfo::List but received: {represented_type:?}" ); } diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 0b18132fba..1a1fcefb63 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,6 +1,6 @@ use core::fmt::{Debug, Formatter}; -use bevy_platform_support::collections::HashTable; +use bevy_platform::collections::HashTable; use bevy_reflect_derive::impl_type_path; use crate::{ @@ -81,12 +81,6 @@ pub trait Map: PartialReflect { /// After calling this function, `self` will be empty. fn drain(&mut self) -> Vec<(Box, Box)>; - /// Clones the map, producing a [`DynamicMap`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_map` instead")] - fn clone_dynamic(&self) -> DynamicMap { - self.to_dynamic_map() - } - /// Creates a new [`DynamicMap`] from this map. fn to_dynamic_map(&self) -> DynamicMap { let mut map = DynamicMap::default(); @@ -242,8 +236,7 @@ impl DynamicMap { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Map(_)), - "expected TypeInfo::Map but received: {:?}", - represented_type + "expected TypeInfo::Map but received: {represented_type:?}" ); } diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 4918179e12..9eb3a3c281 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -218,42 +218,6 @@ where /// See [`ReflectOwned`]. fn reflect_owned(self: Box) -> ReflectOwned; - /// Clones `Self` into its dynamic representation. - /// - /// For value types or types marked with `#[reflect_value]`, - /// this will simply return a clone of `Self`. - /// - /// Otherwise the associated dynamic type will be returned. - /// - /// For example, a [`List`] type will invoke [`List::clone_dynamic`], returning [`DynamicList`]. - /// A [`Struct`] type will invoke [`Struct::clone_dynamic`], returning [`DynamicStruct`]. - /// And so on. - /// - /// If the dynamic behavior is not desired, a concrete clone can be obtained using [`PartialReflect::reflect_clone`]. - /// - /// # Example - /// - /// ``` - /// # use bevy_reflect::{PartialReflect}; - /// let value = (1, true, 3.14); - /// let cloned = value.clone_value(); - /// assert!(cloned.is_dynamic()) - /// ``` - /// - /// [`List`]: crate::List - /// [`List::clone_dynamic`]: crate::List::clone_dynamic - /// [`DynamicList`]: crate::DynamicList - /// [`Struct`]: crate::Struct - /// [`Struct::clone_dynamic`]: crate::Struct::clone_dynamic - /// [`DynamicStruct`]: crate::DynamicStruct - #[deprecated( - since = "0.16.0", - note = "to clone reflected values, prefer using `reflect_clone`. To convert reflected values to dynamic ones, use `to_dynamic`." - )] - fn clone_value(&self) -> Box { - self.to_dynamic() - } - /// Converts this reflected value into its dynamic representation based on its [kind]. /// /// For example, a [`List`] type will internally invoke [`List::to_dynamic_list`], returning [`DynamicList`]. @@ -606,7 +570,7 @@ impl TypePath for dyn Reflect { macro_rules! impl_full_reflect { ($(<$($id:ident),* $(,)?>)? for $ty:ty $(where $($tt:tt)*)?) => { impl $(<$($id),*>)? $crate::Reflect for $ty $(where $($tt)*)? { - fn into_any(self: Box) -> Box { + fn into_any(self: bevy_platform::prelude::Box) -> bevy_platform::prelude::Box { self } @@ -618,7 +582,7 @@ macro_rules! impl_full_reflect { self } - fn into_reflect(self: Box) -> Box { + fn into_reflect(self: bevy_platform::prelude::Box) -> bevy_platform::prelude::Box { self } @@ -632,8 +596,8 @@ macro_rules! impl_full_reflect { fn set( &mut self, - value: Box, - ) -> Result<(), Box> { + value: bevy_platform::prelude::Box, + ) -> Result<(), bevy_platform::prelude::Box> { *self = ::take(value)?; Ok(()) } diff --git a/crates/bevy_reflect/src/serde/de/error_utils.rs b/crates/bevy_reflect/src/serde/de/error_utils.rs index d570c47f0c..58adcfe920 100644 --- a/crates/bevy_reflect/src/serde/de/error_utils.rs +++ b/crates/bevy_reflect/src/serde/de/error_utils.rs @@ -23,7 +23,7 @@ thread_local! { pub(super) fn make_custom_error(msg: impl Display) -> E { #[cfg(feature = "debug_stack")] return TYPE_INFO_STACK - .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + .with_borrow(|stack| E::custom(format_args!("{msg} (stack: {stack:?})"))); #[cfg(not(feature = "debug_stack"))] return E::custom(msg); } diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index c6907b56ec..e82b60bcee 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -30,12 +30,11 @@ mod tests { vec, vec::Vec, }; - use bincode::Options; use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; use serde::{de::DeserializeSeed, Deserialize}; use serde::{de::IgnoredAny, Deserializer}; - use bevy_platform_support::collections::{HashMap, HashSet}; + use bevy_platform::collections::{HashMap, HashSet}; use crate::{ serde::{ @@ -470,10 +469,9 @@ mod tests { let deserializer = ReflectDeserializer::new(®istry); - let dynamic_output = bincode::DefaultOptions::new() - .with_fixint_encoding() - .deserialize_seed(deserializer, &input) - .unwrap(); + let config = bincode::config::standard().with_fixed_int_encoding(); + let (dynamic_output, _read_bytes) = + bincode::serde::seed_decode_from_slice(deserializer, &input, config).unwrap(); let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); assert_eq!(expected, output); diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 2d912fd97b..032590e0c7 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -190,7 +190,7 @@ mod tests { use crate::serde::{ReflectSerializeWithRegistry, SerializeWithRegistry}; use crate::{ReflectFromReflect, TypePath}; use alloc::{format, string::String, vec, vec::Vec}; - use bevy_platform_support::sync::Arc; + use bevy_platform::sync::Arc; use bevy_reflect_derive::reflect_trait; use core::any::TypeId; use core::fmt::{Debug, Formatter}; @@ -400,7 +400,7 @@ mod tests { }; // Poor man's comparison since we can't derive PartialEq for Arc - assert_eq!(format!("{:?}", expected), format!("{:?}", output)); + assert_eq!(format!("{expected:?}"), format!("{output:?}",)); let unexpected = Level { name: String::from("Level 1"), @@ -414,7 +414,7 @@ mod tests { }; // Poor man's comparison since we can't derive PartialEq for Arc - assert_ne!(format!("{:?}", unexpected), format!("{:?}", output)); + assert_ne!(format!("{unexpected:?}"), format!("{output:?}")); } #[test] diff --git a/crates/bevy_reflect/src/serde/ser/error_utils.rs b/crates/bevy_reflect/src/serde/ser/error_utils.rs index d252e7f591..8f38a0742a 100644 --- a/crates/bevy_reflect/src/serde/ser/error_utils.rs +++ b/crates/bevy_reflect/src/serde/ser/error_utils.rs @@ -23,7 +23,7 @@ thread_local! { pub(super) fn make_custom_error(msg: impl Display) -> E { #[cfg(feature = "debug_stack")] return TYPE_INFO_STACK - .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + .with_borrow(|stack| E::custom(format_args!("{msg} (stack: {stack:?})"))); #[cfg(not(feature = "debug_stack"))] return E::custom(msg); } diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index 3844c2a2cb..25399e1d71 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -24,13 +24,14 @@ mod tests { serde::{ReflectSerializer, ReflectSerializerProcessor}, PartialReflect, Reflect, ReflectSerialize, Struct, TypeRegistry, }; + #[cfg(feature = "functions")] + use alloc::boxed::Box; use alloc::{ - boxed::Box, string::{String, ToString}, vec, vec::Vec, }; - use bevy_platform_support::collections::{HashMap, HashSet}; + use bevy_platform::collections::{HashMap, HashSet}; use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; use ron::{extensions::Extensions, ser::PrettyConfig}; use serde::{Serialize, Serializer}; @@ -348,7 +349,8 @@ mod tests { let registry = get_registry(); let serializer = ReflectSerializer::new(&input, ®istry); - let bytes = bincode::serialize(&serializer).unwrap(); + let config = bincode::config::standard().with_fixed_int_encoding(); + let bytes = bincode::serde::encode_to_vec(&serializer, config).unwrap(); let expected: Vec = vec![ 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, diff --git a/crates/bevy_reflect/src/serde/type_data.rs b/crates/bevy_reflect/src/serde/type_data.rs index f1b0129a3c..9bb3e134ac 100644 --- a/crates/bevy_reflect/src/serde/type_data.rs +++ b/crates/bevy_reflect/src/serde/type_data.rs @@ -1,6 +1,6 @@ use crate::Reflect; use alloc::boxed::Box; -use bevy_platform_support::collections::{hash_map::Iter, HashMap}; +use bevy_platform::collections::{hash_map::Iter, HashMap}; /// Contains data relevant to the automatic reflect powered (de)serialization of a type. #[derive(Debug, Clone)] diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs index 21aa7a4d20..01888e7825 100644 --- a/crates/bevy_reflect/src/set.rs +++ b/crates/bevy_reflect/src/set.rs @@ -1,9 +1,7 @@ use alloc::{boxed::Box, format, vec::Vec}; use core::fmt::{Debug, Formatter}; -use bevy_platform_support::collections::{ - hash_table::OccupiedEntry as HashTableOccupiedEntry, HashTable, -}; +use bevy_platform::collections::{hash_table::OccupiedEntry as HashTableOccupiedEntry, HashTable}; use bevy_reflect_derive::impl_type_path; use crate::{ @@ -69,12 +67,6 @@ pub trait Set: PartialReflect { /// After calling this function, `self` will be empty. fn drain(&mut self) -> Vec>; - /// Clones the set, producing a [`DynamicSet`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_set` instead")] - fn clone_dynamic(&self) -> DynamicSet { - self.to_dynamic_set() - } - /// Creates a new [`DynamicSet`] from this set. fn to_dynamic_set(&self) -> DynamicSet { let mut set = DynamicSet::default(); @@ -166,8 +158,7 @@ impl DynamicSet { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Set(_)), - "expected TypeInfo::Set but received: {:?}", - represented_type + "expected TypeInfo::Set but received: {represented_type:?}" ); } @@ -369,7 +360,7 @@ impl FromIterator for DynamicSet { impl IntoIterator for DynamicSet { type Item = Box; - type IntoIter = bevy_platform_support::collections::hash_table::IntoIter; + type IntoIter = bevy_platform::collections::hash_table::IntoIter; fn into_iter(self) -> Self::IntoIter { self.hash_table.into_iter() @@ -379,7 +370,7 @@ impl IntoIterator for DynamicSet { impl<'a> IntoIterator for &'a DynamicSet { type Item = &'a dyn PartialReflect; type IntoIter = core::iter::Map< - bevy_platform_support::collections::hash_table::Iter<'a, Box>, + bevy_platform::collections::hash_table::Iter<'a, Box>, fn(&'a Box) -> Self::Item, >; diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 3f96c74b3c..4346f55e27 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -6,8 +6,8 @@ use crate::{ ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, }; use alloc::{borrow::Cow, boxed::Box, vec::Vec}; -use bevy_platform_support::collections::HashMap; -use bevy_platform_support::sync::Arc; +use bevy_platform::collections::HashMap; +use bevy_platform::sync::Arc; use bevy_reflect_derive::impl_type_path; use core::{ fmt::{Debug, Formatter}, @@ -71,12 +71,6 @@ pub trait Struct: PartialReflect { /// Returns an iterator over the values of the reflectable fields for this struct. fn iter_fields(&self) -> FieldIter; - /// Clones the struct into a [`DynamicStruct`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_struct` instead")] - fn clone_dynamic(&self) -> DynamicStruct { - self.to_dynamic_struct() - } - fn to_dynamic_struct(&self) -> DynamicStruct { let mut dynamic_struct = DynamicStruct::default(); dynamic_struct.set_represented_type(self.get_represented_type_info()); @@ -298,8 +292,7 @@ impl DynamicStruct { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Struct(_)), - "expected TypeInfo::Struct but received: {:?}", - represented_type + "expected TypeInfo::Struct but received: {represented_type:?}" ); } diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 31ad67fdcf..8bdd08099b 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -55,12 +55,6 @@ pub trait Tuple: PartialReflect { /// Drain the fields of this tuple to get a vector of owned values. fn drain(self: Box) -> Vec>; - /// Clones the tuple into a [`DynamicTuple`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_tuple` instead")] - fn clone_dynamic(&self) -> DynamicTuple { - self.to_dynamic_tuple() - } - /// Creates a new [`DynamicTuple`] from this tuple. fn to_dynamic_tuple(&self) -> DynamicTuple { DynamicTuple { @@ -233,8 +227,7 @@ impl DynamicTuple { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::Tuple(_)), - "expected TypeInfo::Tuple but received: {:?}", - represented_type + "expected TypeInfo::Tuple but received: {represented_type:?}" ); } self.represented_type = represented_type; diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index ed8a0c5ea1..ab5b99a96b 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -8,7 +8,7 @@ use crate::{ ReflectOwned, ReflectRef, Tuple, Type, TypeInfo, TypePath, UnnamedField, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use core::{ fmt::{Debug, Formatter}, slice::Iter, @@ -55,12 +55,6 @@ pub trait TupleStruct: PartialReflect { /// Returns an iterator over the values of the tuple struct's fields. fn iter_fields(&self) -> TupleStructFieldIter; - /// Clones the struct into a [`DynamicTupleStruct`]. - #[deprecated(since = "0.16.0", note = "use `to_dynamic_tuple_struct` instead")] - fn clone_dynamic(&self) -> DynamicTupleStruct { - self.to_dynamic_tuple_struct() - } - /// Creates a new [`DynamicTupleStruct`] from this tuple struct. fn to_dynamic_tuple_struct(&self) -> DynamicTupleStruct { DynamicTupleStruct { @@ -248,8 +242,7 @@ impl DynamicTupleStruct { if let Some(represented_type) = represented_type { assert!( matches!(represented_type, TypeInfo::TupleStruct(_)), - "expected TypeInfo::TupleStruct but received: {:?}", - represented_type + "expected TypeInfo::TupleStruct but received: {represented_type:?}" ); } diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index cf80749ede..5827ebdac5 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -1,6 +1,6 @@ use crate::{serde::Serializable, FromReflect, Reflect, TypeInfo, TypePath, Typed}; use alloc::{boxed::Box, string::String}; -use bevy_platform_support::{ +use bevy_platform::{ collections::{HashMap, HashSet}, sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; @@ -250,7 +250,7 @@ impl TypeRegistry { type_id: TypeId, get_registration: impl FnOnce() -> TypeRegistration, ) -> bool { - use bevy_platform_support::collections::hash_map::Entry; + use bevy_platform::collections::hash_map::Entry; match self.registrations.entry(type_id) { Entry::Occupied(_) => false, diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index 33725a4633..5735a29dbe 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -2,7 +2,7 @@ use crate::TypeInfo; use alloc::boxed::Box; -use bevy_platform_support::{ +use bevy_platform::{ hash::{DefaultHasher, FixedHasher, NoOpHash}, sync::{OnceLock, PoisonError, RwLock}, }; diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index d6c26a76c5..173555675f 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -22,7 +22,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", features = [ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } @@ -31,7 +31,7 @@ bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-d anyhow = "1" hyper = { version = "1", features = ["server", "http1"] } serde = { version = "1", features = ["derive"] } -serde_json = { version = "1" } +serde_json = "1.0.140" http-body-util = "0.1" async-channel = "2" diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 60c96dcb8a..18c85d3eec 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -14,24 +14,23 @@ use bevy_ecs::{ system::{In, Local}, world::{EntityRef, EntityWorldMut, FilteredEntityRef, World}, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{ serde::{ReflectSerializer, TypedReflectDeserializer}, GetPath, PartialReflect, TypeRegistration, TypeRegistry, }; -use bevy_utils::default; use serde::{de::DeserializeSeed as _, Deserialize, Serialize}; use serde_json::{Map, Value}; use crate::{ error_codes, - schemas::{ - json_schema::JsonSchemaBevyType, - open_rpc::{OpenRpcDocument, ServerObject}, - }, + schemas::{json_schema::JsonSchemaBevyType, open_rpc::OpenRpcDocument}, BrpError, BrpResult, }; +#[cfg(all(feature = "http", not(target_family = "wasm")))] +use {crate::schemas::open_rpc::ServerObject, bevy_utils::default}; + /// The method path for a `bevy/get` request. pub const BRP_GET_METHOD: &str = "bevy/get"; @@ -719,17 +718,27 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W let app_type_registry = world.resource::().clone(); let type_registry = app_type_registry.read(); - let components = get_component_ids(&type_registry, world, components, strict) + let (components, unregistered_in_components) = + get_component_ids(&type_registry, world, components, strict) + .map_err(BrpError::component_error)?; + let (option, _) = get_component_ids(&type_registry, world, option, strict) .map_err(BrpError::component_error)?; - let option = get_component_ids(&type_registry, world, option, strict) - .map_err(BrpError::component_error)?; - let has = + let (has, unregistered_in_has) = get_component_ids(&type_registry, world, has, strict).map_err(BrpError::component_error)?; - let without = get_component_ids(&type_registry, world, without, strict) + let (without, _) = get_component_ids(&type_registry, world, without, strict) .map_err(BrpError::component_error)?; - let with = get_component_ids(&type_registry, world, with, strict) + let (with, unregistered_in_with) = get_component_ids(&type_registry, world, with, strict) .map_err(BrpError::component_error)?; + // When "strict" is false: + // - Unregistered components in "option" and "without" are ignored. + // - Unregistered components in "has" are considered absent from the entity. + // - Unregistered components in "components" and "with" result in an empty + // response since they specify hard requirements. + if !unregistered_in_components.is_empty() || !unregistered_in_with.is_empty() { + return serde_json::to_value(BrpQueryResponse::default()).map_err(BrpError::internal); + } + let mut query = QueryBuilder::::new(world); for (_, component) in &components { query.ref_id(*component); @@ -785,6 +794,7 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W let has_map = build_has_map( row.clone(), has_paths_and_reflect_components.iter().copied(), + &unregistered_in_has, ); response.push(BrpQueryRow { entity: row.id(), @@ -821,6 +831,8 @@ pub fn process_remote_list_methods_request( world: &mut World, ) -> BrpResult { let remote_methods = world.resource::(); + + #[cfg(all(feature = "http", not(target_family = "wasm")))] let servers = match ( world.get_resource::(), world.get_resource::(), @@ -837,6 +849,10 @@ pub fn process_remote_list_methods_request( }]), _ => None, }; + + #[cfg(any(not(feature = "http"), target_family = "wasm"))] + let servers = None; + let doc = OpenRpcDocument { info: Default::default(), methods: remote_methods.into(), @@ -1019,12 +1035,19 @@ pub fn process_remote_remove_request( let type_registry = app_type_registry.read(); let component_ids = get_component_ids(&type_registry, world, components, true) + .and_then(|(registered, unregistered)| { + if unregistered.is_empty() { + Ok(registered) + } else { + Err(anyhow!("Unregistered component types: {:?}", unregistered)) + } + }) .map_err(BrpError::component_error)?; // Remove the components. let mut entity_world_mut = get_entity_mut(world, entity)?; - for (_, component_id) in component_ids { - entity_world_mut.remove_by_id(component_id); + for (_, component_id) in component_ids.iter() { + entity_world_mut.remove_by_id(*component_id); } Ok(Value::Null) @@ -1259,8 +1282,9 @@ fn get_entity_mut(world: &mut World, entity: Entity) -> Result, strict: bool, -) -> AnyhowResult> { +) -> AnyhowResult<(Vec<(TypeId, ComponentId)>, Vec)> { let mut component_ids = vec![]; + let mut unregistered_components = vec![]; for component_path in component_paths { - let type_id = get_component_type_registration(type_registry, &component_path)?.type_id(); - let Some(component_id) = world.components().get_id(type_id) else { - if strict { - return Err(anyhow!( - "Component `{}` isn't used in the world", - component_path - )); - } - continue; - }; - - component_ids.push((type_id, component_id)); + let maybe_component_tuple = get_component_type_registration(type_registry, &component_path) + .ok() + .and_then(|type_registration| { + let type_id = type_registration.type_id(); + world + .components() + .get_id(type_id) + .map(|component_id| (type_id, component_id)) + }); + if let Some((type_id, component_id)) = maybe_component_tuple { + component_ids.push((type_id, component_id)); + } else if strict { + return Err(anyhow!( + "Component `{}` isn't registered or used in the world", + component_path + )); + } else { + unregistered_components.push(component_path); + } } - Ok(component_ids) + Ok((component_ids, unregistered_components)) } /// Given an entity (`entity_ref`) and a list of reflected component information @@ -1320,12 +1352,16 @@ fn build_components_map<'a>( Ok(serialized_components_map) } -/// Given an entity (`entity_ref`) and list of reflected component information -/// (`paths_and_reflect_components`), return a map which associates each component to -/// a boolean value indicating whether or not that component is present on the entity. +/// Given an entity (`entity_ref`), +/// a list of reflected component information (`paths_and_reflect_components`) +/// and a list of unregistered components, +/// return a map which associates each component to a boolean value indicating +/// whether or not that component is present on the entity. +/// Unregistered components are considered absent from the entity. fn build_has_map<'a>( entity_ref: FilteredEntityRef, paths_and_reflect_components: impl Iterator, + unregistered_components: &[String], ) -> HashMap { let mut has_map = >::default(); @@ -1333,6 +1369,9 @@ fn build_has_map<'a>( let has = reflect_component.contains(entity_ref.clone()); has_map.insert(type_path.to_owned(), Value::Bool(has)); } + unregistered_components.iter().for_each(|component| { + has_map.insert(component.to_owned(), Value::Bool(false)); + }); has_map } @@ -1485,13 +1524,14 @@ mod tests { "Deserialized value does not match original" ); } + use super::*; #[test] fn serialization_tests() { test_serialize_deserialize(BrpQueryRow { components: Default::default(), - entity: Entity::from_raw(0), + entity: Entity::from_raw_u32(0).unwrap(), has: Default::default(), }); test_serialize_deserialize(BrpListWatchingResponse::default()); @@ -1505,7 +1545,7 @@ mod tests { ..Default::default() }); test_serialize_deserialize(BrpListParams { - entity: Entity::from_raw(0), + entity: Entity::from_raw_u32(0).unwrap(), }); } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 86da53b32e..97b2e453e7 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -143,8 +143,8 @@ //! on entities in order for them to be included in results. //! - `without` (optional): An array of fully-qualified type names of components that must *not* be //! present on entities in order for them to be included in results. -//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the -//! components is not present or can not be reflected. Defaults to false. +//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the components +//! is not present or can not be reflected. Defaults to false. //! //! `result`: An array, each of which is an object containing: //! - `entity`: The ID of a query-matching entity. @@ -374,7 +374,7 @@ use bevy_ecs::{ system::{Commands, In, IntoSystem, ResMut, System, SystemId}, world::World, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_utils::prelude::default; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -543,35 +543,39 @@ impl Plugin for RemotePlugin { .add_systems(PreStartup, setup_mailbox_channel) .configure_sets( RemoteLast, - (RemoteSet::ProcessRequests, RemoteSet::Cleanup).chain(), + (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), ) .add_systems( RemoteLast, ( (process_remote_requests, process_ongoing_watching_requests) .chain() - .in_set(RemoteSet::ProcessRequests), - remove_closed_watching_requests.in_set(RemoteSet::Cleanup), + .in_set(RemoteSystems::ProcessRequests), + remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), ), ); } } /// Schedule that contains all systems to process Bevy Remote Protocol requests -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct RemoteLast; /// The systems sets of the [`RemoteLast`] schedule. /// /// These can be useful for ordering. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum RemoteSet { +pub enum RemoteSystems { /// Processing of remote requests. ProcessRequests, /// Cleanup (remove closed watchers etc) Cleanup, } +/// Deprecated alias for [`RemoteSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `RemoteSystems`.")] +pub type RemoteSet = RemoteSystems; + /// A type to hold the allowed types of systems to be used as method handlers. #[derive(Debug)] pub enum RemoteMethodHandler { diff --git a/crates/bevy_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index f7a58006a5..3fcc588f92 100644 --- a/crates/bevy_remote/src/schemas/json_schema.rs +++ b/crates/bevy_remote/src/schemas/json_schema.rs @@ -1,7 +1,7 @@ //! Module with JSON Schema type for Bevy Registry Types. //! It tries to follow this standard: use bevy_ecs::reflect::{ReflectComponent, ReflectResource}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{ prelude::ReflectDefault, NamedField, OpaqueInfo, ReflectDeserialize, ReflectSerialize, TypeInfo, TypeRegistration, VariantInfo, diff --git a/crates/bevy_remote/src/schemas/open_rpc.rs b/crates/bevy_remote/src/schemas/open_rpc.rs index 90a0aee70b..0ffda36bc3 100644 --- a/crates/bevy_remote/src/schemas/open_rpc.rs +++ b/crates/bevy_remote/src/schemas/open_rpc.rs @@ -1,6 +1,6 @@ //! Module with trimmed down `OpenRPC` document structs. //! It tries to follow this standard: -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_utils::default; use serde::{Deserialize, Serialize}; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 9a7f8e51bd..01f1e59861 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -23,7 +23,6 @@ decoupled_naga = [] # Texture formats (require more than just image support) basis-universal = ["bevy_image/basis-universal"] -dds = ["bevy_image/dds"] exr = ["bevy_image/exr"] hdr = ["bevy_image/hdr"] ktx2 = ["dep:ktx2", "bevy_image/ktx2"] @@ -42,7 +41,7 @@ spirv_shader_passthrough = ["wgpu/spirv"] statically-linked-dxc = ["wgpu/static-dxc"] trace = ["profiling"] -tracing-tracy = [] +tracing-tracy = ["dep:tracy-client"] ci_limits = [] webgl = ["wgpu/webgl"] webgpu = ["wgpu/webgpu"] @@ -66,11 +65,13 @@ bevy_render_macros = { path = "macros", version = "0.16.0-dev" } bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } -bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", features = [ + "wgpu_wrapper", +] } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } bevy_image = { path = "../bevy_image", version = "0.16.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } @@ -98,7 +99,7 @@ downcast-rs = { version = "2", default-features = false, features = ["std"] } thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = ["from"] } futures-lite = "2.0.1" -ktx2 = { version = "0.3.0", optional = true } +ktx2 = { version = "0.4.0", optional = true } encase = { version = "0.10", features = ["glam"] } # For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. profiling = { version = "1", features = [ @@ -110,6 +111,7 @@ smallvec = { version = "1.11", features = ["const_new"] } offset-allocator = "0.2" variadics_please = "1.1" tracing = { version = "0.1", default-features = false, features = ["std"] } +tracy-client = { version = "0.18.0", optional = true } indexmap = { version = "2" } fixedbitset = { version = "0.5" } bitflags = "2" @@ -144,16 +146,13 @@ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = fa bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [ "web", ] } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "web", ] } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ "web", ] } -[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies] -send_wrapper = "0.6.0" - [lints] workspace = true diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index e44d87a3d4..4252929170 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -6,7 +6,7 @@ use syn::{ parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, - token::Comma, + token::{Comma, DotDot}, Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, MetaList, Result, }; @@ -20,6 +20,9 @@ const BINDLESS_ATTRIBUTE_NAME: Symbol = Symbol("bindless"); const DATA_ATTRIBUTE_NAME: Symbol = Symbol("data"); const BINDING_ARRAY_MODIFIER_NAME: Symbol = Symbol("binding_array"); const LIMIT_MODIFIER_NAME: Symbol = Symbol("limit"); +const INDEX_TABLE_MODIFIER_NAME: Symbol = Symbol("index_table"); +const RANGE_MODIFIER_NAME: Symbol = Symbol("range"); +const BINDING_MODIFIER_NAME: Symbol = Symbol("binding"); #[derive(Copy, Clone, Debug)] enum BindingType { @@ -48,6 +51,12 @@ enum BindlessSlabResourceLimitAttr { Limit(LitInt), } +// The `bindless(index_table(range(M..N)))` attribute. +struct BindlessIndexTableRangeAttr { + start: LitInt, + end: LitInt, +} + pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let manifest = BevyManifest::shared(); let render_path = manifest.get_path("bevy_render"); @@ -65,6 +74,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { // After the first attribute pass, this will be `None` if the object isn't // bindless and `Some` if it is. let mut attr_bindless_count = None; + let mut attr_bindless_index_table_range = None; + let mut attr_bindless_index_table_binding = None; // `actual_bindless_slot_count` holds the actual number of bindless slots // per bind group, taking into account whether the current platform supports @@ -88,28 +99,54 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { attr_prepared_data_ident = Some(prepared_data_ident); } } else if attr_ident == BINDLESS_ATTRIBUTE_NAME { - match attr.meta { - Meta::Path(_) => { - attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto); - } - Meta::List(_) => { - // Parse bindless features. For now, the only one we - // support is `limit(N)`. - attr.parse_nested_meta(|submeta| { - if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) { - let content; - parenthesized!(content in submeta.input); - let lit: LitInt = content.parse()?; + attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Auto); + if let Meta::List(_) = attr.meta { + // Parse bindless features. + attr.parse_nested_meta(|submeta| { + if submeta.path.is_ident(&LIMIT_MODIFIER_NAME) { + let content; + parenthesized!(content in submeta.input); + let lit: LitInt = content.parse()?; - attr_bindless_count = - Some(BindlessSlabResourceLimitAttr::Limit(lit)); - return Ok(()); - } + attr_bindless_count = Some(BindlessSlabResourceLimitAttr::Limit(lit)); + return Ok(()); + } - Err(Error::new_spanned(attr, "Expected `limit(N)`")) - })?; - } - _ => {} + if submeta.path.is_ident(&INDEX_TABLE_MODIFIER_NAME) { + submeta.parse_nested_meta(|subsubmeta| { + if subsubmeta.path.is_ident(&RANGE_MODIFIER_NAME) { + let content; + parenthesized!(content in subsubmeta.input); + let start: LitInt = content.parse()?; + content.parse::()?; + let end: LitInt = content.parse()?; + attr_bindless_index_table_range = + Some(BindlessIndexTableRangeAttr { start, end }); + return Ok(()); + } + + if subsubmeta.path.is_ident(&BINDING_MODIFIER_NAME) { + let content; + parenthesized!(content in subsubmeta.input); + let lit: LitInt = content.parse()?; + + attr_bindless_index_table_binding = Some(lit); + return Ok(()); + } + + Err(Error::new_spanned( + attr, + "Expected `range(M..N)` or `binding(N)`", + )) + })?; + return Ok(()); + } + + Err(Error::new_spanned( + attr, + "Expected `limit` or `index_table`", + )) + })?; } } } @@ -521,7 +558,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { binding_impls.insert(0, quote! { ( #binding_index, #render_path::render_resource::OwnedBindingResource::TextureView( - #dimension, + #render_path::render_resource::#dimension, { let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { @@ -881,6 +918,33 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { None => quote! { 0 }, }; + // Calculate the actual bindless index table range, taking the + // `#[bindless(index_table(range(M..N)))]` attribute into account. + let bindless_index_table_range = match attr_bindless_index_table_range { + None => { + let resource_count = bindless_resource_types.len() as u32; + quote! { + #render_path::render_resource::BindlessIndex(0).. + #render_path::render_resource::BindlessIndex(#resource_count) + } + } + Some(BindlessIndexTableRangeAttr { start, end }) => { + quote! { + #render_path::render_resource::BindlessIndex(#start).. + #render_path::render_resource::BindlessIndex(#end) + } + } + }; + + // Calculate the actual binding number of the bindless index table, taking + // the `#[bindless(index_table(binding(B)))]` into account. + let bindless_index_table_binding_number = match attr_bindless_index_table_binding { + None => quote! { #render_path::render_resource::BindingNumber(0) }, + Some(binding_number) => { + quote! { #render_path::render_resource::BindingNumber(#binding_number) } + } + }; + // Calculate the actual number of bindless slots, taking hardware // limitations into account. let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) = @@ -942,9 +1006,18 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ]> = ::std::sync::LazyLock::new(|| { [#(#bindless_buffer_descriptors),*] }); + static INDEX_TABLES: &[ + #render_path::render_resource::BindlessIndexTableDescriptor + ] = &[ + #render_path::render_resource::BindlessIndexTableDescriptor { + indices: #bindless_index_table_range, + binding_number: #bindless_index_table_binding_number, + } + ]; Some(#render_path::render_resource::BindlessDescriptor { resources: ::std::borrow::Cow::Borrowed(RESOURCES), buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS), + index_tables: ::std::borrow::Cow::Borrowed(&*INDEX_TABLES), }) }; @@ -964,8 +1037,6 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ), }; - let bindless_resource_count = bindless_resource_types.len() as u32; - Ok(TokenStream::from(quote! { #(#field_struct_impls)* @@ -1011,10 +1082,13 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let mut #bind_group_layout_entries = Vec::new(); match #actual_bindless_slot_count { Some(bindless_slot_count) => { + let bindless_index_table_range = #bindless_index_table_range; #bind_group_layout_entries.extend( #render_path::render_resource::create_bindless_bind_group_layout_entries( - #bindless_resource_count, + bindless_index_table_range.end.0 - + bindless_index_table_range.start.0, bindless_slot_count.into(), + #bindless_index_table_binding_number, ).into_iter() ); #(#bindless_binding_layouts)*; diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 7a04932bcd..75cbdfa959 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -80,12 +80,10 @@ pub fn derive_render_label(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("render_graph").into()); - let mut dyn_eq_path = trait_path.clone(); trait_path .segments .push(format_ident!("RenderLabel").into()); - dyn_eq_path.segments.push(format_ident!("DynEq").into()); - derive_label(input, "RenderLabel", &trait_path, &dyn_eq_path) + derive_label(input, "RenderLabel", &trait_path) } /// Derive macro generating an impl of the trait `RenderSubGraph`. @@ -98,10 +96,8 @@ pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("render_graph").into()); - let mut dyn_eq_path = trait_path.clone(); trait_path .segments .push(format_ident!("RenderSubGraph").into()); - dyn_eq_path.segments.push(format_ident!("DynEq").into()); - derive_label(input, "RenderSubGraph", &trait_path, &dyn_eq_path) + derive_label(input, "RenderSubGraph", &trait_path) } diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index b2db3a0f92..ea5970431a 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -14,13 +14,13 @@ use bevy_ecs::{ }; use bevy_encase_derive::ShaderType; use bevy_math::UVec4; -use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet}; +use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_utils::{default, TypeIdMap}; use bytemuck::{Pod, Zeroable}; use encase::{internal::WriteInto, ShaderSize}; use indexmap::IndexMap; use nonmax::NonMaxU32; -use tracing::error; +use tracing::{error, info}; use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; use crate::{ @@ -36,7 +36,7 @@ use crate::{ renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::MainEntity, view::{ExtractedView, NoIndirectDrawing, RetainedViewEntity}, - Render, RenderApp, RenderDebugFlags, RenderSet, + Render, RenderApp, RenderDebugFlags, RenderSystems, }; use super::{BatchMeta, GetBatchData, GetFullBatchData}; @@ -60,11 +60,11 @@ impl Plugin for BatchingPlugin { )) .add_systems( Render, - write_indirect_parameters_buffers.in_set(RenderSet::PrepareResourcesFlush), + write_indirect_parameters_buffers.in_set(RenderSystems::PrepareResourcesFlush), ) .add_systems( Render, - clear_indirect_parameters_buffers.in_set(RenderSet::ManageViews), + clear_indirect_parameters_buffers.in_set(RenderSystems::ManageViews), ); } @@ -111,6 +111,11 @@ impl GpuPreprocessingSupport { } } } + + /// Returns true if GPU culling is supported on this platform. + pub fn is_culling_supported(&self) -> bool { + self.max_supported_mode == GpuPreprocessingMode::Culling + } } /// The amount of GPU preprocessing (compute and indirect draw) that we do. @@ -387,6 +392,10 @@ where } /// The buffer of GPU preprocessing work items for a single view. +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] pub enum PreprocessWorkItemBuffers { /// The work items we use if we aren't using indirect drawing. /// @@ -1088,32 +1097,46 @@ impl FromWorld for GpuPreprocessingSupport { let adapter = world.resource::(); let device = world.resource::(); - // Filter some Qualcomm devices on Android as they crash when using GPU - // preprocessing. - // We filter out Adreno 730 and earlier GPUs (except 720, as it's newer - // than 730). + // Filter Android drivers that are incompatible with GPU preprocessing: + // - We filter out Adreno 730 and earlier GPUs (except 720, as it's newer + // than 730). + // - We filter out Mali GPUs with driver versions lower than 48. fn is_non_supported_android_device(adapter: &RenderAdapter) -> bool { crate::get_adreno_model(adapter).is_some_and(|model| model != 720 && model <= 730) + || crate::get_mali_driver_version(adapter).is_some_and(|version| version < 48) } - let feature_support = device.features().contains( + let culling_feature_support = device.features().contains( Features::INDIRECT_FIRST_INSTANCE | Features::MULTI_DRAW_INDIRECT | Features::PUSH_CONSTANTS, ); // Depth downsampling for occlusion culling requires 12 textures - let limit_support = device.limits().max_storage_textures_per_shader_stage >= 12; + let limit_support = device.limits().max_storage_textures_per_shader_stage >= 12 && + // Even if the adapter supports compute, we might be simulating a lack of + // compute via device limits (see `WgpuSettingsPriority::WebGL2` and + // `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the + // `max_compute_*` limits to zero, so we arbitrarily pick one as a canary. + device.limits().max_compute_workgroup_storage_size != 0; + let downlevel_support = adapter.get_downlevel_capabilities().flags.contains( + DownlevelFlags::COMPUTE_SHADERS | DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW ); let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 || is_non_supported_android_device(adapter) { + info!( + "GPU preprocessing is not supported on this device. \ + Falling back to CPU preprocessing.", + ); GpuPreprocessingMode::None - } else if !(feature_support && limit_support && downlevel_support) { + } else if !(culling_feature_support && limit_support && downlevel_support) { + info!("Some GPU preprocessing are limited on this device."); GpuPreprocessingMode::PreprocessingOnly } else { + info!("GPU preprocessing is fully supported on this device."); GpuPreprocessingMode::Culling }; diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index c2751fc79a..40ce7ce3b4 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -187,6 +187,7 @@ where phase.multidrawable_meshes.sort_unstable_keys(); phase.batchable_meshes.sort_unstable_keys(); phase.unbatchable_meshes.sort_unstable_keys(); + phase.non_mesh_items.sort_unstable_keys(); } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d38cb3fbca..2828486fd4 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -13,7 +13,7 @@ use crate::{ sync_world::{RenderEntity, SyncToRenderWorld}, texture::GpuImage, view::{ - ColorGrading, ExtractedView, ExtractedWindows, Msaa, NoIndirectDrawing, RenderLayers, + ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing, RenderLayers, RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, Visibility, VisibleEntities, }, Extract, @@ -23,7 +23,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, component::{Component, HookContext}, - entity::{Entity, EntityBorrow}, + entity::{ContainsEntity, Entity}, event::EventReader, prelude::With, query::Has, @@ -34,7 +34,7 @@ use bevy_ecs::{ }; use bevy_image::Image; use bevy_math::{ops, vec2, Dir3, FloatOrd, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3}; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::prelude::*; use bevy_render_macros::ExtractComponent; use bevy_transform::components::{GlobalTransform, Transform}; @@ -356,9 +356,6 @@ pub struct Camera { pub computed: ComputedCameraValues, /// The "target" that this camera will render to. pub target: RenderTarget, - /// If this is set to `true`, the camera will use an intermediate "high dynamic range" render texture. - /// This allows rendering with a wider range of lighting values. - pub hdr: bool, // todo: reflect this when #6042 lands /// The [`CameraOutputMode`] for this camera. #[reflect(ignore, clone)] @@ -389,7 +386,6 @@ impl Default for Camera { computed: Default::default(), target: Default::default(), output_mode: Default::default(), - hdr: false, msaa_writeback: true, clear_color: Default::default(), sub_camera_view: None, @@ -1101,6 +1097,7 @@ pub fn extract_cameras( &GlobalTransform, &VisibleEntities, &Frustum, + Has, Option<&ColorGrading>, Option<&Exposure>, Option<&TemporalJitter>, @@ -1122,6 +1119,7 @@ pub fn extract_cameras( transform, visible_entities, frustum, + hdr, color_grading, exposure, temporal_jitter, @@ -1200,14 +1198,14 @@ pub fn extract_cameras( exposure: exposure .map(Exposure::exposure) .unwrap_or_else(|| Exposure::default().exposure()), - hdr: camera.hdr, + hdr, }, ExtractedView { retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0), clip_from_view: camera.clip_from_view(), world_from_view: *transform, clip_from_world: None, - hdr: camera.hdr, + hdr, viewport: UVec4::new( viewport_origin.x, viewport_origin.y, diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 7fa221235f..8be5a345b4 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -4,8 +4,8 @@ use crate::{ renderer::RenderContext, view::ExtractedWindows, }; -use bevy_ecs::{entity::EntityBorrow, prelude::QueryState, world::World}; -use bevy_platform_support::collections::HashSet; +use bevy_ecs::{entity::ContainsEntity, prelude::QueryState, world::World}; +use bevy_platform::collections::HashSet; use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp}; pub struct CameraDriverNode { diff --git a/crates/bevy_render/src/camera/manual_texture_view.rs b/crates/bevy_render/src/camera/manual_texture_view.rs index 7f76b8e886..56eff5612a 100644 --- a/crates/bevy_render/src/camera/manual_texture_view.rs +++ b/crates/bevy_render/src/camera/manual_texture_view.rs @@ -2,7 +2,7 @@ use crate::{extract_resource::ExtractResource, render_resource::TextureView}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent, resource::Resource}; use bevy_image::BevyDefault as _; use bevy_math::UVec2; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::prelude::*; use wgpu::TextureFormat; diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 4c77021bad..a2470a7660 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -12,7 +12,7 @@ pub use projection::*; use crate::{ extract_component::ExtractComponentPlugin, extract_resource::ExtractResourcePlugin, - render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSet, + render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; use bevy_ecs::schedule::IntoScheduleConfigs; @@ -42,7 +42,7 @@ impl Plugin for CameraPlugin { render_app .init_resource::() .add_systems(ExtractSchedule, extract_cameras) - .add_systems(Render, sort_cameras.in_set(RenderSet::ManageViews)); + .add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews)); let camera_driver_node = CameraDriverNode::new(render_app.world_mut()); let mut render_graph = render_app.world_mut().resource_mut::(); render_graph.add_node(crate::graph::CameraDriverLabel, camera_driver_node); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index e3f95cb036..a7796a1d1a 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -2,12 +2,12 @@ use core::fmt::Debug; use crate::{primitives::Frustum, view::VisibilitySystems}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; -use bevy_asset::AssetEvents; +use bevy_asset::AssetEventSystems; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize}; -use bevy_transform::{components::GlobalTransform, TransformSystem}; +use bevy_transform::{components::GlobalTransform, TransformSystems}; use derive_more::derive::From; use serde::{Deserialize, Serialize}; @@ -25,18 +25,18 @@ impl Plugin for CameraProjectionPlugin { .register_type::() .add_systems( PostStartup, - crate::camera::camera_system.in_set(CameraUpdateSystem), + crate::camera::camera_system.in_set(CameraUpdateSystems), ) .add_systems( PostUpdate, ( crate::camera::camera_system - .in_set(CameraUpdateSystem) - .before(AssetEvents), + .in_set(CameraUpdateSystems) + .before(AssetEventSystems), crate::view::update_frusta .in_set(VisibilitySystems::UpdateFrusta) .after(crate::camera::camera_system) - .after(TransformSystem::TransformPropagate), + .after(TransformSystems::Propagate), ), ); } @@ -46,7 +46,11 @@ impl Plugin for CameraProjectionPlugin { /// /// [`camera_system`]: crate::camera::camera_system #[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)] -pub struct CameraUpdateSystem; +pub struct CameraUpdateSystems; + +/// Deprecated alias for [`CameraUpdateSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `CameraUpdateSystems`.")] +pub type CameraUpdateSystem = CameraUpdateSystems; /// Describes a type that can generate a projection matrix, allowing it to be added to a /// [`Camera`]'s [`Projection`] component. diff --git a/crates/bevy_render/src/diagnostic/internal.rs b/crates/bevy_render/src/diagnostic/internal.rs index 8445ebe54d..e7005f70f3 100644 --- a/crates/bevy_render/src/diagnostic/internal.rs +++ b/crates/bevy_render/src/diagnostic/internal.rs @@ -8,14 +8,15 @@ use std::thread::{self, ThreadId}; use bevy_diagnostic::{Diagnostic, DiagnosticMeasurement, DiagnosticPath, DiagnosticsStore}; use bevy_ecs::resource::Resource; use bevy_ecs::system::{Res, ResMut}; -use bevy_platform_support::time::Instant; +use bevy_platform::time::Instant; use std::sync::Mutex; use wgpu::{ Buffer, BufferDescriptor, BufferUsages, CommandEncoder, ComputePass, Features, MapMode, - PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, Queue, RenderPass, + PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, RenderPass, }; -use crate::renderer::{RenderDevice, WgpuWrapper}; +use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue}; +use bevy_utils::WgpuWrapper; use super::RecordDiagnostics; @@ -32,6 +33,8 @@ struct DiagnosticsRecorderInternal { current_frame: Mutex, submitted_frames: Vec, finished_frames: Vec, + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context: tracy_client::GpuContext, } /// Records diagnostics into [`QuerySet`]'s keeping track of the mapping between @@ -41,21 +44,31 @@ pub struct DiagnosticsRecorder(WgpuWrapper); impl DiagnosticsRecorder { /// Creates the new `DiagnosticsRecorder`. - pub fn new(device: &RenderDevice, queue: &Queue) -> DiagnosticsRecorder { + pub fn new( + adapter_info: &RenderAdapterInfo, + device: &RenderDevice, + queue: &RenderQueue, + ) -> DiagnosticsRecorder { let features = device.features(); - let timestamp_period_ns = if features.contains(Features::TIMESTAMP_QUERY) { - queue.get_timestamp_period() - } else { - 0.0 - }; + #[cfg(feature = "tracing-tracy")] + let tracy_gpu_context = + super::tracy_gpu::new_tracy_gpu_context(adapter_info, device, queue); + let _ = adapter_info; // Prevent unused variable warnings when tracing-tracy is not enabled DiagnosticsRecorder(WgpuWrapper::new(DiagnosticsRecorderInternal { - timestamp_period_ns, + timestamp_period_ns: queue.get_timestamp_period(), features, - current_frame: Mutex::new(FrameData::new(device, features)), + current_frame: Mutex::new(FrameData::new( + device, + features, + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context.clone(), + )), submitted_frames: Vec::new(), finished_frames: Vec::new(), + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context, })) } @@ -86,7 +99,7 @@ impl DiagnosticsRecorder { /// Copies data from [`QuerySet`]'s to a [`Buffer`], after which it can be downloaded to CPU. /// - /// Should be called before [`DiagnosticsRecorder::finish_frame`] + /// Should be called before [`DiagnosticsRecorder::finish_frame`]. pub fn resolve(&mut self, encoder: &mut CommandEncoder) { self.current_frame_mut().resolve(encoder); } @@ -102,6 +115,9 @@ impl DiagnosticsRecorder { device: &RenderDevice, callback: impl FnOnce(RenderDiagnostics) + Send + Sync + 'static, ) { + #[cfg(feature = "tracing-tracy")] + let tracy_gpu_context = self.0.tracy_gpu_context.clone(); + let internal = &mut self.0; internal .current_frame @@ -112,7 +128,12 @@ impl DiagnosticsRecorder { // reuse one of the finished frames, if we can let new_frame = match internal.finished_frames.pop() { Some(frame) => frame, - None => FrameData::new(device, internal.features), + None => FrameData::new( + device, + internal.features, + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context, + ), }; let old_frame = core::mem::replace( @@ -169,10 +190,16 @@ struct FrameData { closed_spans: Vec, is_mapped: Arc, callback: Option>, + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context: tracy_client::GpuContext, } impl FrameData { - fn new(device: &RenderDevice, features: Features) -> FrameData { + fn new( + device: &RenderDevice, + features: Features, + #[cfg(feature = "tracing-tracy")] tracy_gpu_context: tracy_client::GpuContext, + ) -> FrameData { let wgpu_device = device.wgpu_device(); let mut buffer_size = 0; @@ -237,6 +264,8 @@ impl FrameData { closed_spans: Vec::new(), is_mapped: Arc::new(AtomicBool::new(false)), callback: None, + #[cfg(feature = "tracing-tracy")] + tracy_gpu_context, } } @@ -502,6 +531,19 @@ impl FrameData { let end = timestamps[end as usize] as f64; let value = (end - begin) * (timestamp_period_ns as f64) / 1e6; + #[cfg(feature = "tracing-tracy")] + { + // Calling span_alloc() and end_zone() here instead of in open_span() and close_span() means that tracy does not know where each GPU command was recorded on the CPU timeline. + // Unfortunately we must do it this way, because tracy does not play nicely with multithreaded command recording. The start/end pairs would get all mixed up. + // The GPU spans themselves are still accurate though, and it's probably safe to assume that each GPU span in frame N belongs to the corresponding CPU render node span from frame N-1. + let name = &self.path_components[span.path_range.clone()].join("/"); + let mut tracy_gpu_span = + self.tracy_gpu_context.span_alloc(name, "", "", 0).unwrap(); + tracy_gpu_span.end_zone(); + tracy_gpu_span.upload_timestamp_start(begin as i64); + tracy_gpu_span.upload_timestamp_end(end as i64); + } + diagnostics.push(RenderDiagnostic { path: self.diagnostic_path(&span.path_range, "elapsed_gpu"), suffix: "ms", diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 09b6052c10..7f046036a9 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -3,13 +3,15 @@ //! For more info, see [`RenderDiagnosticsPlugin`]. pub(crate) mod internal; +#[cfg(feature = "tracing-tracy")] +mod tracy_gpu; use alloc::{borrow::Cow, sync::Arc}; use core::marker::PhantomData; use bevy_app::{App, Plugin, PreUpdate}; -use crate::RenderApp; +use crate::{renderer::RenderAdapterInfo, RenderApp}; use self::internal::{ sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, @@ -20,8 +22,8 @@ use super::{RenderDevice, RenderQueue}; /// Enables collecting render diagnostics, such as CPU/GPU elapsed time per render pass, /// as well as pipeline statistics (number of primitives, number of shader invocations, etc). /// -/// To access the diagnostics, you can use [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource, -/// or add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin). +/// To access the diagnostics, you can use the [`DiagnosticsStore`](bevy_diagnostic::DiagnosticsStore) resource, +/// add [`LogDiagnosticsPlugin`](bevy_diagnostic::LogDiagnosticsPlugin), or use [Tracy](https://github.com/bevyengine/bevy/blob/main/docs/profiling.md#tracy-renderqueue). /// /// To record diagnostics in your own passes: /// 1. First, obtain the diagnostic recorder using [`RenderContext::diagnostic_recorder`](crate::renderer::RenderContext::diagnostic_recorder). @@ -62,9 +64,10 @@ impl Plugin for RenderDiagnosticsPlugin { return; }; + let adapter_info = render_app.world().resource::(); let device = render_app.world().resource::(); let queue = render_app.world().resource::(); - render_app.insert_resource(DiagnosticsRecorder::new(device, queue)); + render_app.insert_resource(DiagnosticsRecorder::new(adapter_info, device, queue)); } } diff --git a/crates/bevy_render/src/diagnostic/tracy_gpu.rs b/crates/bevy_render/src/diagnostic/tracy_gpu.rs new file mode 100644 index 0000000000..c059b8baa5 --- /dev/null +++ b/crates/bevy_render/src/diagnostic/tracy_gpu.rs @@ -0,0 +1,67 @@ +use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue}; +use tracy_client::{Client, GpuContext, GpuContextType}; +use wgpu::{ + Backend, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Maintain, MapMode, + QuerySetDescriptor, QueryType, QUERY_SIZE, +}; + +pub fn new_tracy_gpu_context( + adapter_info: &RenderAdapterInfo, + device: &RenderDevice, + queue: &RenderQueue, +) -> GpuContext { + let tracy_gpu_backend = match adapter_info.backend { + Backend::Vulkan => GpuContextType::Vulkan, + Backend::Dx12 => GpuContextType::Direct3D12, + Backend::Gl => GpuContextType::OpenGL, + Backend::Metal | Backend::BrowserWebGpu | Backend::Empty => GpuContextType::Invalid, + }; + + let tracy_client = Client::running().unwrap(); + tracy_client + .new_gpu_context( + Some("RenderQueue"), + tracy_gpu_backend, + initial_timestamp(device, queue), + queue.get_timestamp_period(), + ) + .unwrap() +} + +// Code copied from https://github.com/Wumpf/wgpu-profiler/blob/f9de342a62cb75f50904a98d11dd2bbeb40ceab8/src/tracy.rs +fn initial_timestamp(device: &RenderDevice, queue: &RenderQueue) -> i64 { + let query_set = device.wgpu_device().create_query_set(&QuerySetDescriptor { + label: None, + ty: QueryType::Timestamp, + count: 1, + }); + + let resolve_buffer = device.create_buffer(&BufferDescriptor { + label: None, + size: QUERY_SIZE as _, + usage: BufferUsages::QUERY_RESOLVE | BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let map_buffer = device.create_buffer(&BufferDescriptor { + label: None, + size: QUERY_SIZE as _, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let mut timestamp_encoder = device.create_command_encoder(&CommandEncoderDescriptor::default()); + timestamp_encoder.write_timestamp(&query_set, 0); + timestamp_encoder.resolve_query_set(&query_set, 0..1, &resolve_buffer, 0); + // Workaround for https://github.com/gfx-rs/wgpu/issues/6406 + // TODO when that bug is fixed, merge these encoders together again + let mut copy_encoder = device.create_command_encoder(&CommandEncoderDescriptor::default()); + copy_encoder.copy_buffer_to_buffer(&resolve_buffer, 0, &map_buffer, 0, QUERY_SIZE as _); + queue.submit([timestamp_encoder.finish(), copy_encoder.finish()]); + + map_buffer.slice(..).map_async(MapMode::Read, |_| ()); + device.poll(Maintain::Wait); + + let view = map_buffer.slice(..).get_mapped_range(); + i64::from_le_bytes((*view).try_into().unwrap()) +} diff --git a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs index a3b067e19f..77fcb4b5b2 100644 --- a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs +++ b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs @@ -4,19 +4,13 @@ //! Bevy. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use crate::{ - extract_component::ExtractComponent, - render_resource::{Shader, TextureView}, + extract_component::ExtractComponent, load_shader_library, render_resource::TextureView, }; -/// The handle to the `mesh_preprocess_types.wgsl` compute shader. -pub const MESH_PREPROCESS_TYPES_SHADER_HANDLE: Handle = - weak_handle!("7bf7bdb1-ec53-4417-987f-9ec36533287c"); - /// Enables GPU occlusion culling. /// /// See [`OcclusionCulling`] for a detailed description of occlusion culling in @@ -25,12 +19,7 @@ pub struct OcclusionCullingPlugin; impl Plugin for OcclusionCullingPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - MESH_PREPROCESS_TYPES_SHADER_HANDLE, - "mesh_preprocess_types.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "mesh_preprocess_types.wgsl"); } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 19d15a2b86..e1f528d6ab 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -4,7 +4,7 @@ use crate::{ sync_component::SyncComponentPlugin, sync_world::RenderEntity, view::ViewVisibility, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; use bevy_ecs::{ @@ -70,7 +70,7 @@ pub trait ExtractComponent: Component { /// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted /// for every processed entity. /// -/// Therefore it sets up the [`RenderSet::Prepare`] step +/// Therefore it sets up the [`RenderSystems::Prepare`] step /// for the specified [`ExtractComponent`]. pub struct UniformComponentPlugin(PhantomData C>); @@ -87,7 +87,7 @@ impl Plugin for UniformComponentP .insert_resource(ComponentUniforms::::default()) .add_systems( Render, - prepare_uniform_components::.in_set(RenderSet::PrepareResources), + prepare_uniform_components::.in_set(RenderSystems::PrepareResources), ); } } diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 6ac7079bc5..ac6b04ff0f 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -2,7 +2,10 @@ use crate::MainWorld; use bevy_ecs::{ component::Tick, prelude::*, - system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState}, + system::{ + ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemParamValidationError, + SystemState, + }, world::unsafe_world_cell::UnsafeWorldCell, }; use core::ops::{Deref, DerefMut}; @@ -78,22 +81,23 @@ where #[inline] unsafe fn validate_param( - state: &Self::State, - system_meta: &SystemMeta, + state: &mut Self::State, + _system_meta: &SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to world data registered in `init_state`. let result = unsafe { world.get_resource_by_id(state.main_world_state) }; let Some(main_world) = result else { - system_meta.try_warn_param::<&World>(); - return false; + return Err(SystemParamValidationError::invalid::( + "`MainWorld` resource does not exist", + )); }; // SAFETY: Type is guaranteed by `SystemState`. let main_world: &World = unsafe { main_world.deref() }; // SAFETY: We provide the main world on which this system state was initialized on. unsafe { SystemState::

::validate_param( - &state.state, + &mut state.state, main_world.as_unsafe_world_cell_readonly(), ) } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index c05d96c4c9..04e4109f09 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -1,25 +1,21 @@ use crate::{ extract_resource::ExtractResource, - prelude::Shader, + load_shader_library, render_resource::{ShaderType, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_diagnostic::FrameCount; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; use bevy_time::Time; -pub const GLOBALS_TYPE_HANDLE: Handle = - weak_handle!("9e22a765-30ca-4070-9a4c-34ac08f1c0e7"); - pub struct GlobalsPlugin; impl Plugin for GlobalsPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, GLOBALS_TYPE_HANDLE, "globals.wgsl", Shader::from_wgsl); + load_shader_library!(app, "globals.wgsl"); app.register_type::(); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { @@ -29,7 +25,7 @@ impl Plugin for GlobalsPlugin { .add_systems(ExtractSchedule, (extract_frame_count, extract_time)) .add_systems( Render, - prepare_globals_buffer.in_set(RenderSet::PrepareResources), + prepare_globals_buffer.in_set(RenderSystems::PrepareResources), ); } } diff --git a/crates/bevy_render/src/gpu_component_array_buffer.rs b/crates/bevy_render/src/gpu_component_array_buffer.rs index b3f78f5bfb..b4eb56197f 100644 --- a/crates/bevy_render/src/gpu_component_array_buffer.rs +++ b/crates/bevy_render/src/gpu_component_array_buffer.rs @@ -1,7 +1,7 @@ use crate::{ render_resource::{GpuArrayBuffer, GpuArrayBufferable}, renderer::{RenderDevice, RenderQueue}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; use bevy_ecs::{ @@ -20,7 +20,7 @@ impl Plugin for GpuComponentArrayBufferPlugin if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems( Render, - prepare_gpu_component_array_buffers::.in_set(RenderSet::PrepareResources), + prepare_gpu_component_array_buffers::.in_set(RenderSystems::PrepareResources), ); } } diff --git a/crates/bevy_render/src/gpu_readback.rs b/crates/bevy_render/src/gpu_readback.rs index 5c25e437cb..c05861f3da 100644 --- a/crates/bevy_render/src/gpu_readback.rs +++ b/crates/bevy_render/src/gpu_readback.rs @@ -9,7 +9,7 @@ use crate::{ storage::{GpuShaderStorageBuffer, ShaderStorageBuffer}, sync_world::MainEntity, texture::GpuImage, - ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, + ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems, }; use async_channel::{Receiver, Sender}; use bevy_app::{App, Plugin}; @@ -24,7 +24,7 @@ use bevy_ecs::{ system::{Query, Res}, }; use bevy_image::{Image, TextureFormatPixelInfo}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; use bevy_render_macros::ExtractComponent; use encase::internal::ReadFrom; @@ -60,8 +60,10 @@ impl Plugin for GpuReadbackPlugin { .add_systems( Render, ( - prepare_buffers.in_set(RenderSet::PrepareResources), - map_buffers.after(render_system).in_set(RenderSet::Render), + prepare_buffers.in_set(RenderSystems::PrepareResources), + map_buffers + .after(render_system) + .in_set(RenderSystems::Render), ), ); } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index a75a7eb87c..d520990f93 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -72,7 +72,14 @@ pub mod prelude { }; } use batching::gpu_preprocessing::BatchingPlugin; + +#[doc(hidden)] +pub mod _macro { + pub use bevy_asset; +} + use bevy_ecs::schedule::ScheduleBuildSettings; +use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats}; use bevy_utils::prelude::default; pub use extract_param::Extract; @@ -86,7 +93,8 @@ use render_asset::{ use renderer::{RenderAdapter, RenderDevice, RenderQueue}; use settings::RenderResources; use sync_world::{ - despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, SyncWorldPlugin, + despawn_temporary_render_entities, entity_sync_system, MainEntity, RenderEntity, + SyncToRenderWorld, SyncWorldPlugin, TemporaryRenderEntity, }; use crate::gpu_readback::GpuReadbackPlugin; @@ -95,20 +103,39 @@ use crate::{ mesh::{MeshPlugin, MorphPlugin, RenderMesh}, render_asset::prepare_assets, render_resource::{PipelineCache, Shader, ShaderLoader}, - renderer::{render_system, RenderInstance, WgpuWrapper}, + renderer::{render_system, RenderInstance}, settings::RenderCreation, storage::StoragePlugin, view::{ViewPlugin, WindowRenderPlugin}, }; use alloc::sync::Arc; use bevy_app::{App, AppLabel, Plugin, SubApp}; -use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetServer, Handle}; +use bevy_asset::{AssetApp, AssetServer}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; +use bevy_utils::WgpuWrapper; use bitflags::bitflags; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; use tracing::debug; +/// Inline shader as an `embedded_asset` and load it permanently. +/// +/// This works around a limitation of the shader loader not properly loading +/// dependencies of shaders. +#[macro_export] +macro_rules! load_shader_library { + ($asset_server_provider: expr, $path: literal $(, $settings: expr)?) => { + $crate::_macro::bevy_asset::embedded_asset!($asset_server_provider, $path); + let handle: $crate::_macro::bevy_asset::prelude::Handle<$crate::prelude::Shader> = + $crate::_macro::bevy_asset::load_embedded_asset!( + $asset_server_provider, + $path + $(,$settings)? + ); + core::mem::forget(handle); + } +} + /// Contains the default Bevy rendering backend based on wgpu. /// /// Rendering is done in a [`SubApp`], which exchanges data with the main app @@ -144,7 +171,7 @@ bitflags! { /// /// These can be useful for ordering, but you almost never want to add your systems to these sets. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum RenderSet { +pub enum RenderSystems { /// This is used for applying the commands from the [`ExtractSchedule`] ExtractCommands, /// Prepare assets that have been created/modified/removed this frame. @@ -156,9 +183,9 @@ pub enum RenderSet { /// Queue drawable entities as phase items in render phases ready for /// sorting (if necessary) Queue, - /// A sub-set within [`Queue`](RenderSet::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::` is completed. + /// A sub-set within [`Queue`](RenderSystems::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::` is completed. QueueMeshes, - /// A sub-set within [`Queue`](RenderSet::Queue) where meshes that have + /// A sub-set within [`Queue`](RenderSystems::Queue) where meshes that have /// become invisible or changed phases are removed from the bins. QueueSweep, // TODO: This could probably be moved in favor of a system ordering @@ -169,14 +196,14 @@ pub enum RenderSet { /// Prepare render resources from extracted data for the GPU based on their sorted order. /// Create [`BindGroups`](render_resource::BindGroup) that depend on those data. Prepare, - /// A sub-set within [`Prepare`](RenderSet::Prepare) for initializing buffers, textures and uniforms for use in bind groups. + /// A sub-set within [`Prepare`](RenderSystems::Prepare) for initializing buffers, textures and uniforms for use in bind groups. PrepareResources, /// Collect phase buffers after - /// [`PrepareResources`](RenderSet::PrepareResources) has run. + /// [`PrepareResources`](RenderSystems::PrepareResources) has run. PrepareResourcesCollectPhaseBuffers, - /// Flush buffers after [`PrepareResources`](RenderSet::PrepareResources), but before [`PrepareBindGroups`](RenderSet::PrepareBindGroups). + /// Flush buffers after [`PrepareResources`](RenderSystems::PrepareResources), but before [`PrepareBindGroups`](RenderSystems::PrepareBindGroups). PrepareResourcesFlush, - /// A sub-set within [`Prepare`](RenderSet::Prepare) for constructing bind groups, or other data that relies on render resources prepared in [`PrepareResources`](RenderSet::PrepareResources). + /// A sub-set within [`Prepare`](RenderSystems::Prepare) for constructing bind groups, or other data that relies on render resources prepared in [`PrepareResources`](RenderSystems::PrepareResources). PrepareBindGroups, /// Actual rendering happens here. /// In most cases, only the render backend should insert resources here. @@ -185,12 +212,16 @@ pub enum RenderSet { Cleanup, /// Final cleanup occurs: all entities will be despawned. /// - /// Runs after [`Cleanup`](RenderSet::Cleanup). + /// Runs after [`Cleanup`](RenderSystems::Cleanup). PostCleanup, } +/// Deprecated alias for [`RenderSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `RenderSystems`.")] +pub type RenderSet = RenderSystems; + /// The main render schedule. -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct Render; impl Render { @@ -198,7 +229,7 @@ impl Render { /// /// The sets defined in this enum are configured to run in order. pub fn base_schedule() -> Schedule { - use RenderSet::*; + use RenderSystems::*; let mut schedule = Schedule::new(Self); @@ -246,7 +277,7 @@ impl Render { /// /// This schedule is run on the main world, but its buffers are not applied /// until it is returned to the render world. -#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash)] +#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)] pub struct ExtractSchedule; /// The simulation [`World`] of the application, stored as a resource. @@ -285,17 +316,8 @@ struct FutureRenderResources(Arc>>); #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderApp; -pub const INSTANCE_INDEX_SHADER_HANDLE: Handle = - weak_handle!("475c76aa-4afd-4a6b-9878-1fc1e2f41216"); -pub const MATHS_SHADER_HANDLE: Handle = - weak_handle!("d94d70d4-746d-49c4-bfc3-27d63f2acda0"); -pub const COLOR_OPERATIONS_SHADER_HANDLE: Handle = - weak_handle!("33a80b2f-aaf7-4c86-b828-e7ae83b72f1a"); -pub const BINDLESS_SHADER_HANDLE: Handle = - weak_handle!("13f1baaa-41bf-448e-929e-258f9307a522"); - impl Plugin for RenderPlugin { - /// Initializes the renderer, sets up the [`RenderSet`] and creates the rendering sub-app. + /// Initializes the renderer, sets up the [`RenderSystems`] and creates the rendering sub-app. fn build(&self, app: &mut App) { app.init_asset::() .init_asset_loader::(); @@ -408,6 +430,8 @@ impl Plugin for RenderPlugin { StoragePlugin, GpuReadbackPlugin::default(), OcclusionCullingPlugin, + #[cfg(feature = "tracing-tracy")] + diagnostic::RenderDiagnosticsPlugin, )); app.init_resource::(); @@ -417,7 +441,7 @@ impl Plugin for RenderPlugin { .add_systems(ExtractSchedule, extract_render_asset_bytes_per_frame) .add_systems( Render, - reset_render_asset_bytes_per_frame.in_set(RenderSet::Cleanup), + reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup), ); } @@ -428,6 +452,9 @@ impl Plugin for RenderPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::(); } @@ -439,29 +466,24 @@ impl Plugin for RenderPlugin { } fn finish(&self, app: &mut App) { - load_internal_asset!(app, MATHS_SHADER_HANDLE, "maths.wgsl", Shader::from_wgsl); - load_internal_asset!( - app, - COLOR_OPERATIONS_SHADER_HANDLE, - "color_operations.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - BINDLESS_SHADER_HANDLE, - "bindless.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "maths.wgsl"); + load_shader_library!(app, "color_operations.wgsl"); + load_shader_library!(app, "bindless.wgsl"); if let Some(future_render_resources) = app.world_mut().remove_resource::() { let RenderResources(device, queue, adapter_info, render_adapter, instance) = future_render_resources.0.lock().unwrap().take().unwrap(); + let compressed_image_format_support = CompressedImageFormatSupport( + CompressedImageFormats::from_features(device.features()), + ); + app.insert_resource(device.clone()) .insert_resource(queue.clone()) .insert_resource(adapter_info.clone()) - .insert_resource(render_adapter.clone()); + .insert_resource(render_adapter.clone()) + .insert_resource(compressed_image_format_support); let render_app = app.sub_app_mut(RenderApp); @@ -528,11 +550,11 @@ unsafe fn initialize_render_app(app: &mut App) { ( // This set applies the commands from the extract schedule while the render schedule // is running in parallel with the main app. - apply_extract_commands.in_set(RenderSet::ExtractCommands), + apply_extract_commands.in_set(RenderSystems::ExtractCommands), (PipelineCache::process_pipeline_queue_system, render_system) .chain() - .in_set(RenderSet::Render), - despawn_temporary_render_entities.in_set(RenderSet::PostCleanup), + .in_set(RenderSystems::Render), + despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup), ), ); @@ -584,3 +606,26 @@ pub fn get_adreno_model(adapter: &RenderAdapter) -> Option { .fold(0, |acc, digit| acc * 10 + digit), ) } + +/// Get the Mali driver version if the adapter is a Mali GPU. +pub fn get_mali_driver_version(adapter: &RenderAdapter) -> Option { + if !cfg!(target_os = "android") { + return None; + } + + let driver_name = adapter.get_info().name; + if !driver_name.contains("Mali") { + return None; + } + let driver_info = adapter.get_info().driver_info; + if let Some(start_pos) = driver_info.find("v1.r") { + if let Some(end_pos) = driver_info[start_pos..].find('p') { + let start_idx = start_pos + 4; // Skip "v1.r" + let end_idx = start_pos + end_pos; + + return driver_info[start_idx..end_idx].parse::().ok(); + } + } + + None +} diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 3653fc1e87..eb2d4de626 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -16,7 +16,7 @@ use bevy_ecs::{ system::{Res, ResMut}, world::{FromWorld, World}, }; -use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet}; +use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_utils::default; use offset_allocator::{Allocation, Allocator}; use tracing::error; @@ -30,7 +30,7 @@ use crate::{ render_asset::{prepare_assets, ExtractedAssets}, render_resource::Buffer, renderer::{RenderAdapter, RenderDevice, RenderQueue}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; /// A plugin that manages GPU memory for mesh data. @@ -158,6 +158,10 @@ pub struct MeshBufferSlice<'a> { pub struct SlabId(pub NonMaxU32); /// Data for a single slab. +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] enum Slab { /// A slab that can contain multiple objects. General(GeneralSlab), @@ -311,7 +315,7 @@ impl Plugin for MeshAllocatorPlugin { .add_systems( Render, allocate_and_free_meshes - .in_set(RenderSet::PrepareAssets) + .in_set(RenderSystems::PrepareAssets) .before(prepare_assets::), ); } @@ -358,7 +362,10 @@ pub fn allocate_and_free_meshes( render_device: Res, render_queue: Res, ) { - // Process newly-added meshes. + // Process removed or modified meshes. + mesh_allocator.free_meshes(&extracted_meshes); + + // Process newly-added or modified meshes. mesh_allocator.allocate_meshes( &mesh_allocator_settings, &extracted_meshes, @@ -366,9 +373,6 @@ pub fn allocate_and_free_meshes( &render_device, &render_queue, ); - - // Process removed meshes. - mesh_allocator.free_meshes(&extracted_meshes); } impl MeshAllocator { @@ -607,9 +611,17 @@ impl MeshAllocator { } } + /// Frees allocations for meshes that were removed or modified this frame. fn free_meshes(&mut self, extracted_meshes: &ExtractedAssets) { let mut empty_slabs = >::default(); - for mesh_id in &extracted_meshes.removed { + + // TODO: Consider explicitly reusing allocations for changed meshes of the same size + let meshes_to_free = extracted_meshes + .removed + .iter() + .chain(extracted_meshes.modified.iter()); + + for mesh_id in meshes_to_free { if let Some(slab_id) = self.mesh_id_to_vertex_slab.remove(mesh_id) { self.free_allocation_in_slab(mesh_id, slab_id, &mut empty_slabs); } diff --git a/crates/bevy_render/src/mesh/components.rs b/crates/bevy_render/src/mesh/components.rs index 772ff1f9e5..000de324e3 100644 --- a/crates/bevy_render/src/mesh/components.rs +++ b/crates/bevy_render/src/mesh/components.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ change_detection::DetectChangesMut, component::Component, event::EventReader, reflect::ReflectComponent, system::Query, }; -use bevy_platform_support::{collections::HashSet, hash::FixedHasher}; +use bevy_platform::{collections::HashSet, hash::FixedHasher}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::components::Transform; use derive_more::derive::From; diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index fbd530c14d..d15468376f 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -13,7 +13,7 @@ use crate::{ }; use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin, PostUpdate}; -use bevy_asset::{AssetApp, AssetEvents, AssetId, RenderAssetUsages}; +use bevy_asset::{AssetApp, AssetEventSystems, AssetId, RenderAssetUsages}; use bevy_ecs::{ prelude::*, system::{ @@ -73,7 +73,7 @@ impl Plugin for MeshPlugin { PostUpdate, mark_3d_meshes_as_changed_if_their_assets_changed .ambiguous_with(VisibilitySystems::CalculateBounds) - .before(AssetEvents), + .before(AssetEventSystems), ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index fb665e469d..efe728e7c7 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -97,7 +97,7 @@ impl Drop for RenderAppChannels { /// This is run on the main app's thread. /// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the /// main schedule can start sooner. -/// - Then the `rendering schedule` is run. See [`RenderSet`](crate::RenderSet) for the standard steps in this process. +/// - Then the `rendering schedule` is run. See [`RenderSystems`](crate::RenderSystems) for the standard steps in this process. /// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By /// default, this schedule is empty. But it is useful if you need something to run before I/O processing. /// - Next all the `winit events` are processed. diff --git a/crates/bevy_render/src/primitives/mod.rs b/crates/bevy_render/src/primitives/mod.rs index ef67997ecf..ca664fc338 100644 --- a/crates/bevy_render/src/primitives/mod.rs +++ b/crates/bevy_render/src/primitives/mod.rs @@ -1,6 +1,6 @@ use core::borrow::Borrow; -use bevy_ecs::{component::Component, entity::hash_map::EntityHashMap, reflect::ReflectComponent}; +use bevy_ecs::{component::Component, entity::EntityHashMap, reflect::ReflectComponent}; use bevy_math::{Affine3A, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles}; use bevy_reflect::prelude::*; diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index acc299b3d7..1fa5758ca3 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -1,6 +1,6 @@ use crate::{ render_resource::AsBindGroupError, Extract, ExtractSchedule, MainWorld, Render, RenderApp, - RenderSet, Res, + RenderSystems, Res, }; use bevy_app::{App, Plugin, SubApp}; pub use bevy_asset::RenderAssetUsages; @@ -11,7 +11,7 @@ use bevy_ecs::{ system::{ScheduleSystem, StaticSystemParam, SystemParam, SystemParamItem, SystemState}, world::{FromWorld, Mut}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use core::marker::PhantomData; use core::sync::atomic::{AtomicUsize, Ordering}; use thiserror::Error; @@ -27,14 +27,18 @@ pub enum PrepareAssetError { /// The system set during which we extract modified assets to the render world. #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] -pub struct ExtractAssetsSet; +pub struct AssetExtractionSystems; + +/// Deprecated alias for [`AssetExtractionSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `AssetExtractionSystems`.")] +pub type ExtractAssetsSet = AssetExtractionSystems; /// Describes how an asset gets extracted and prepared for rendering. /// /// In the [`ExtractSchedule`] step the [`RenderAsset::SourceAsset`] is transferred /// from the "main world" into the "render world". /// -/// After that in the [`RenderSet::PrepareAssets`] step the extracted asset +/// After that in the [`RenderSystems::PrepareAssets`] step the extracted asset /// is transformed into its GPU-representation of type [`RenderAsset`]. pub trait RenderAsset: Send + Sync + 'static + Sized { /// The representation of the asset in the "main world". @@ -88,7 +92,7 @@ pub trait RenderAsset: Send + Sync + 'static + Sized { /// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource. /// /// Therefore it sets up the [`ExtractSchedule`] and -/// [`RenderSet::PrepareAssets`] steps for the specified [`RenderAsset`]. +/// [`RenderSystems::PrepareAssets`] steps for the specified [`RenderAsset`]. /// /// The `AFTER` generic parameter can be used to specify that `A::prepare_asset` should not be run until /// `prepare_assets::` has completed. This allows the `prepare_asset` function to depend on another @@ -120,11 +124,11 @@ impl Plugin .init_resource::>() .add_systems( ExtractSchedule, - extract_render_asset::.in_set(ExtractAssetsSet), + extract_render_asset::.in_set(AssetExtractionSystems), ); AFTER::register_system( render_app, - prepare_assets::.in_set(RenderSet::PrepareAssets), + prepare_assets::.in_set(RenderSystems::PrepareAssets), ); } } @@ -151,14 +155,19 @@ impl RenderAssetDependency for A { #[derive(Resource)] pub struct ExtractedAssets { /// The assets extracted this frame. + /// + /// These are assets that were either added or modified this frame. pub extracted: Vec<(AssetId, A::SourceAsset)>, - /// IDs of the assets removed this frame. + /// IDs of the assets that were removed this frame. /// /// These assets will not be present in [`ExtractedAssets::extracted`]. pub removed: HashSet>, - /// IDs of the assets added this frame. + /// IDs of the assets that were modified this frame. + pub modified: HashSet>, + + /// IDs of the assets that were added this frame. pub added: HashSet>, } @@ -167,6 +176,7 @@ impl Default for ExtractedAssets { Self { extracted: Default::default(), removed: Default::default(), + modified: Default::default(), added: Default::default(), } } @@ -235,8 +245,9 @@ pub(crate) fn extract_render_asset( |world, mut cached_state: Mut>| { let (mut events, mut assets) = cached_state.state.get_mut(world); - let mut changed_assets = >::default(); + let mut needs_extracting = >::default(); let mut removed = >::default(); + let mut modified = >::default(); for event in events.read() { #[expect( @@ -244,12 +255,20 @@ pub(crate) fn extract_render_asset( reason = "LoadedWithDependencies is marked as a TODO, so it's likely this will no longer lint soon." )] match event { - AssetEvent::Added { id } | AssetEvent::Modified { id } => { - changed_assets.insert(*id); + AssetEvent::Added { id } => { + needs_extracting.insert(*id); + } + AssetEvent::Modified { id } => { + needs_extracting.insert(*id); + modified.insert(*id); + } + AssetEvent::Removed { .. } => { + // We don't care that the asset was removed from Assets in the main world. + // An asset is only removed from RenderAssets when its last handle is dropped (AssetEvent::Unused). } - AssetEvent::Removed { .. } => {} AssetEvent::Unused { id } => { - changed_assets.remove(id); + needs_extracting.remove(id); + modified.remove(id); removed.insert(*id); } AssetEvent::LoadedWithDependencies { .. } => { @@ -260,7 +279,7 @@ pub(crate) fn extract_render_asset( let mut extracted_assets = Vec::new(); let mut added = >::default(); - for id in changed_assets.drain() { + for id in needs_extracting.drain() { if let Some(asset) = assets.get(id) { let asset_usage = A::asset_usage(asset); if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { @@ -280,6 +299,7 @@ pub(crate) fn extract_render_asset( commands.insert_resource(ExtractedAssets:: { extracted: extracted_assets, removed, + modified, added, }); cached_state.state.apply(world); diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index 78e6cbf324..d1a7020bcf 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -6,7 +6,7 @@ use crate::{ renderer::RenderContext, }; use bevy_ecs::{define_label, intern::Interned, prelude::World, resource::Resource}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use core::fmt::Debug; use super::{EdgeExistence, InternedRenderLabel, IntoRenderNodeArray}; @@ -680,7 +680,7 @@ mod tests { renderer::RenderContext, }; use bevy_ecs::world::{FromWorld, World}; - use bevy_platform_support::collections::HashSet; + use bevy_platform::collections::HashSet; #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] enum TestLabel { diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index a12d336018..374dc6b330 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -315,7 +315,6 @@ where /// Prepares the render command to be used. This is called once and only once before the phase /// begins. There may be zero or more [`draw`](RenderCommandState::draw) calls following a call to this function. fn prepare(&mut self, world: &'_ World) { - self.state.update_archetypes(world); self.view.update_archetypes(world); self.entity.update_archetypes(world); } @@ -328,7 +327,7 @@ where view: Entity, item: &P, ) -> Result<(), DrawError> { - let param = self.state.get_manual(world); + let param = self.state.get(world); let view = match self.view.get_manual(world, view) { Ok(view) => view, Err(err) => match err { diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index fae06f0c60..272418f67f 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -10,10 +10,10 @@ //! //! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these //! render phases for each view that it is visible in. -//! This must be done in the [`RenderSet::Queue`]. -//! After that the render phase sorts them in the [`RenderSet::PhaseSort`]. +//! This must be done in the [`RenderSystems::Queue`]. +//! After that the render phase sorts them in the [`RenderSystems::PhaseSort`]. //! Finally the items are rendered using a single [`TrackedRenderPass`], during -//! the [`RenderSet::Render`]. +//! the [`RenderSystems::Render`]. //! //! Therefore each phase item is assigned a [`Draw`] function. //! These set up the state of the [`TrackedRenderPass`] (i.e. select the @@ -32,7 +32,7 @@ use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::component::Tick; use bevy_ecs::entity::EntityHash; -use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_utils::default; pub use draw::*; pub use draw_state::*; @@ -59,7 +59,7 @@ use crate::{ GetFullBatchData, }, render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache}, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use bevy_ecs::{ prelude::*, @@ -67,6 +67,7 @@ use bevy_ecs::{ }; use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex}; use smallvec::SmallVec; +use tracing::warn; /// Stores the rendering instructions for a single phase that uses bins in all /// views. @@ -129,7 +130,7 @@ where /// entity are simply called in order at rendering time. /// /// See the `custom_phase_item` example for an example of how to use this. - pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, + pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>, /// Information on each batch set. /// @@ -165,6 +166,9 @@ where /// remove the entity from the old bin during /// [`BinnedRenderPhase::sweep_old_entities`]. entities_that_changed_bins: Vec>, + /// The gpu preprocessing mode configured for the view this phase is associated + /// with. + gpu_preprocessing_mode: GpuPreprocessingMode, } /// All entities that share a mesh and a material and can be batched as part of @@ -322,6 +326,12 @@ pub struct UnbatchableBinnedEntities { pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet, } +/// Information about [`BinnedRenderPhaseType::NonMesh`] entities. +pub struct NonMeshEntities { + /// The entities. + pub entities: MainEntityHashMap, +} + /// Stores instance indices and dynamic offsets for unbatchable entities in a /// binned render phase. /// @@ -375,14 +385,12 @@ pub enum BinnedRenderPhaseType { /// can be batched with other meshes of the same type. MultidrawableMesh, - /// The item is a mesh that's eligible for single-draw indirect rendering - /// and can be batched with other meshes of the same type. + /// The item is a mesh that can be batched with other meshes of the same type and + /// drawn in a single draw call. BatchableMesh, /// The item is a mesh that's eligible for indirect rendering, but can't be /// batched with other meshes of the same type. - /// - /// At the moment, this is used for skinned meshes. UnbatchableMesh, /// The item isn't a mesh at all. @@ -462,9 +470,17 @@ where bin_key: BPI::BinKey, (entity, main_entity): (Entity, MainEntity), input_uniform_index: InputUniformIndex, - phase_type: BinnedRenderPhaseType, + mut phase_type: BinnedRenderPhaseType, change_tick: Tick, ) { + // If the user has overridden indirect drawing for this view, we need to + // force the phase type to be batchable instead. + if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly + && phase_type == BinnedRenderPhaseType::MultidrawableMesh + { + phase_type = BinnedRenderPhaseType::BatchableMesh; + } + match phase_type { BinnedRenderPhaseType::MultidrawableMesh => { match self.multidrawable_meshes.entry(batch_set_key.clone()) { @@ -526,10 +542,12 @@ where .entry((batch_set_key.clone(), bin_key.clone()).clone()) { indexmap::map::Entry::Occupied(mut entry) => { - entry.get_mut().insert(main_entity, input_uniform_index); + entry.get_mut().entities.insert(main_entity, entity); } indexmap::map::Entry::Vacant(entry) => { - entry.insert(RenderBin::from_entity(main_entity, input_uniform_index)); + let mut entities = MainEntityHashMap::default(); + entities.insert(main_entity, entity); + entry.insert(NonMeshEntities { entities }); } } } @@ -795,14 +813,14 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - for ((batch_set_key, bin_key), bin) in &self.non_mesh_items { - for &entity in bin.entities.keys() { + for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items { + for (main_entity, entity) in non_mesh_entities.entities.iter() { // Come up with a fake batch range and extra index. The draw // function is expected to manage any sort of batching logic itself. let binned_phase_item = BPI::new( batch_set_key.clone(), bin_key.clone(), - (Entity::PLACEHOLDER, entity), + (*entity, *main_entity), 0..1, PhaseItemExtraIndex::None, ); @@ -836,6 +854,10 @@ where .set_range(self.cached_entity_bin_keys.len().., true); self.entities_that_changed_bins.clear(); + + for unbatchable_bin in self.unbatchable_meshes.values_mut() { + unbatchable_bin.buffer_indices.clear(); + } } /// Checks to see whether the entity is in a bin and returns true if it's @@ -921,7 +943,7 @@ fn remove_entity_from_bin( multidrawable_meshes: &mut IndexMap>, batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, - non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, + non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>, ) where BPI: BinnedPhaseItem, { @@ -984,10 +1006,10 @@ fn remove_entity_from_bin( entity_bin_key.batch_set_key.clone(), entity_bin_key.bin_key.clone(), )) { - bin_entry.get_mut().remove(entity); + bin_entry.get_mut().entities.remove(&entity); // If the bin is now empty, remove the bin. - if bin_entry.get_mut().is_empty() { + if bin_entry.get_mut().entities.is_empty() { bin_entry.swap_remove(); } } @@ -1017,6 +1039,7 @@ where cached_entity_bin_keys: IndexMap::default(), valid_cached_entity_bin_keys: FixedBitSet::new(), entities_that_changed_bins: vec![], + gpu_preprocessing_mode: gpu_preprocessing, } } } @@ -1111,7 +1134,7 @@ where .add_systems( Render, ( - batching::sort_binned_render_phase::.in_set(RenderSet::PhaseSort), + batching::sort_binned_render_phase::.in_set(RenderSystems::PhaseSort), ( no_gpu_preprocessing::batch_and_prepare_binned_render_phase:: .run_if(resource_exists::>), @@ -1122,15 +1145,15 @@ where >, ), ) - .in_set(RenderSet::PrepareResources), - sweep_old_entities::.in_set(RenderSet::QueueSweep), + .in_set(RenderSystems::PrepareResources), + sweep_old_entities::.in_set(RenderSystems::QueueSweep), gpu_preprocessing::collect_buffers_for_phase:: .run_if( resource_exists::< BatchedInstanceBuffers, >, ) - .in_set(RenderSet::PrepareResourcesCollectPhaseBuffers), + .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers), ), ); } @@ -1227,14 +1250,14 @@ where >, ), ) - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), gpu_preprocessing::collect_buffers_for_phase:: .run_if( resource_exists::< BatchedInstanceBuffers, >, ) - .in_set(RenderSet::PrepareResourcesCollectPhaseBuffers), + .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers), ), ); } @@ -1307,6 +1330,10 @@ impl UnbatchableBinnedEntityIndexSet { // but let's go ahead and do the sensible thing anyhow: demote // the compressed `NoDynamicOffsets` field to the full // `DynamicOffsets` array. + warn!( + "Unbatchable binned entity index set was demoted from sparse to dense. \ + This is a bug in the renderer. Please report it.", + ); let new_dynamic_offsets = (0..instance_range.len() as u32) .flat_map(|entity_index| self.indices_for_entity_index(entity_index)) .chain(iter::once(indices)) @@ -1319,6 +1346,17 @@ impl UnbatchableBinnedEntityIndexSet { } } } + + /// Clears the unbatchable binned entity index set. + fn clear(&mut self) { + match self { + UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(), + UnbatchableBinnedEntityIndexSet::Sparse { .. } => { + *self = UnbatchableBinnedEntityIndexSet::NoEntities; + } + _ => {} + } + } } /// A collection of all items to be rendered that will be encoded to GPU @@ -1427,10 +1465,10 @@ where /// /// The data required for rendering an entity is extracted from the main world in the /// [`ExtractSchedule`](crate::ExtractSchedule). -/// Then it has to be queued up for rendering during the [`RenderSet::Queue`], +/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`], /// by adding a corresponding phase item to a render phase. /// Afterwards it will be possibly sorted and rendered automatically in the -/// [`RenderSet::PhaseSort`] and [`RenderSet::Render`], respectively. +/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively. /// /// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and /// [`SortedPhaseItem`]s. diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 02f4e09351..cff88bd355 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -1,4 +1,3 @@ -use crate::renderer::WgpuWrapper; use crate::{ define_atomic_id, render_asset::RenderAssets, @@ -9,6 +8,7 @@ use crate::{ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{SystemParam, SystemParamItem}; pub use bevy_render_macros::AsBindGroup; +use bevy_utils::WgpuWrapper; use core::ops::Deref; use encase::ShaderType; use thiserror::Error; @@ -369,12 +369,12 @@ impl Deref for BindGroup { /// available. Because not all platforms support bindless resources, you /// should check for the presence of this definition via `#ifdef` and fall /// back to standard bindings if it isn't present. -/// * In bindless mode, binding 0 becomes the *bindless index table*, which is -/// an array of structures, each of which contains as many fields of type `u32` -/// as the highest binding number in the structure annotated with -/// `#[derive(AsBindGroup)]`. The *i*th field of the bindless index table -/// contains the index of the resource with binding *i* within the appropriate -/// binding array. +/// * By default, in bindless mode, binding 0 becomes the *bindless index +/// table*, which is an array of structures, each of which contains as many +/// fields of type `u32` as the highest binding number in the structure +/// annotated with `#[derive(AsBindGroup)]`. Again by default, the *i*th field +/// of the bindless index table contains the index of the resource with binding +/// *i* within the appropriate binding array. /// * In the case of materials, the index of the applicable table within the /// bindless index table list corresponding to the mesh currently being drawn /// can be retrieved with @@ -384,12 +384,30 @@ impl Deref for BindGroup { /// each slab will have no more than 16 total resources in it. If you don't /// specify a limit, Bevy automatically picks a reasonable one for the current /// platform. +/// * The `index_table(range(M..N), binding(B))` declaration allows you to +/// customize the layout of the bindless index table. This is useful for +/// materials that are composed of multiple bind groups, such as +/// `ExtendedMaterial`. In such cases, there will be multiple bindless index +/// tables, so they can't both be assigned to binding 0 or their bindings will +/// conflict. +/// - The `binding(B)` attribute of the `index_table` attribute allows you to +/// customize the binding (`@binding(B)`, in the shader) at which the index +/// table will be bound. +/// - The `range(M, N)` attribute of the `index_table` attribute allows you to +/// change the mapping from the field index in the bindless index table to the +/// bindless index. Instead of the field at index $i$ being mapped to the +/// bindless index $i$, with the `range(M, N)` attribute the field at index +/// $i$ in the bindless index table is mapped to the bindless index $i$ + M. +/// The size of the index table will be set to N - M. Note that this may +/// result in the table being too small to contain all the bindless bindings. /// * The purpose of bindless mode is to improve performance by reducing /// state changes. By grouping resources together into binding arrays, Bevy /// doesn't have to modify GPU state as often, decreasing API and driver /// overhead. -/// * See the `shaders/shader_material_bindless` example for an example of -/// how to use bindless mode. +/// * See the `shaders/shader_material_bindless` example for an example of how +/// to use bindless mode. See the `shaders/extended_material_bindless` example +/// for a more exotic example of bindless mode that demonstrates the +/// `index_table` attribute. /// * The following diagram illustrates how bindless mode works using a subset /// of `StandardMaterial`: /// diff --git a/crates/bevy_render/src/render_resource/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs index e19f5b969f..2d674f46d1 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -1,5 +1,5 @@ use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; +use bevy_utils::WgpuWrapper; use core::ops::Deref; define_atomic_id!(BindGroupLayoutId); diff --git a/crates/bevy_render/src/render_resource/bindless.rs b/crates/bevy_render/src/render_resource/bindless.rs index 6734abc572..64a0fa2c1f 100644 --- a/crates/bevy_render/src/render_resource/bindless.rs +++ b/crates/bevy_render/src/render_resource/bindless.rs @@ -1,7 +1,10 @@ //! Types and functions relating to bindless resources. use alloc::borrow::Cow; -use core::num::{NonZeroU32, NonZeroU64}; +use core::{ + num::{NonZeroU32, NonZeroU64}, + ops::Range, +}; use bevy_derive::{Deref, DerefMut}; use wgpu::{ @@ -102,6 +105,11 @@ pub struct BindlessDescriptor { /// /// The order of this array is irrelevant. pub buffers: Cow<'static, [BindlessBufferDescriptor]>, + /// The [`BindlessIndexTableDescriptor`]s describing each bindless index + /// table. + /// + /// This list must be sorted by the first bindless index. + pub index_tables: Cow<'static, [BindlessIndexTableDescriptor]>, } /// The type of potentially-bindless resource. @@ -165,7 +173,7 @@ pub enum BindlessResourceType { /// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, /// bindless(BINDING_NUMBER)]`, the bindless index is `BINDLESS_INDEX`, and the /// binding number is `BINDING_NUMBER`. Note the order. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct BindlessBufferDescriptor { /// The actual binding number of the buffer. /// @@ -185,6 +193,19 @@ pub struct BindlessBufferDescriptor { pub size: Option, } +/// Describes the layout of the bindless index table, which maps bindless +/// indices to indices within the binding arrays. +#[derive(Clone)] +pub struct BindlessIndexTableDescriptor { + /// The range of bindless indices that this descriptor covers. + pub indices: Range, + /// The binding at which the index table itself will be bound. + /// + /// By default, this is binding 0, but it can be changed with the + /// `#[bindless(index_table(binding(B)))]` attribute. + pub binding_number: BindingNumber, +} + /// The index of the actual binding in the bind group. /// /// This is the value specified in WGSL as `@binding`. @@ -194,7 +215,7 @@ pub struct BindingNumber(pub u32); /// The index in the bindless index table. /// /// This table is conventionally bound to binding number 0. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Hash, Debug, Deref, DerefMut)] pub struct BindlessIndex(pub u32); /// Creates the bind group layout entries common to all shaders that use @@ -204,8 +225,9 @@ pub struct BindlessIndex(pub u32); /// `bindless_slab_resource_limit` specifies the resolved /// [`BindlessSlabResourceLimit`] value. pub fn create_bindless_bind_group_layout_entries( - bindless_resource_count: u32, + bindless_index_table_length: u32, bindless_slab_resource_limit: u32, + bindless_index_table_binding_number: BindingNumber, ) -> Vec { let bindless_slab_resource_limit = NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero"); @@ -219,10 +241,10 @@ pub fn create_bindless_bind_group_layout_entries( // Start with the bindless index table, bound to binding number 0. storage_buffer_read_only_sized( false, - NonZeroU64::new(bindless_resource_count as u64 * size_of::() as u64), + NonZeroU64::new(bindless_index_table_length as u64 * size_of::() as u64), ) - .build(0, ShaderStages::all()), - // Continue with the common bindless buffers. + .build(*bindless_index_table_binding_number, ShaderStages::all()), + // Continue with the common bindless resource arrays. sampler(SamplerBindingType::Filtering) .count(bindless_slab_resource_limit) .build(1, ShaderStages::all()), diff --git a/crates/bevy_render/src/render_resource/buffer.rs b/crates/bevy_render/src/render_resource/buffer.rs index 9b7bb2c41f..b779417bf5 100644 --- a/crates/bevy_render/src/render_resource/buffer.rs +++ b/crates/bevy_render/src/render_resource/buffer.rs @@ -1,5 +1,5 @@ use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; +use bevy_utils::WgpuWrapper; use core::ops::{Bound, Deref, RangeBounds}; define_atomic_id!(BufferId); diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index aab2fe5b6a..b777d96290 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -55,10 +55,10 @@ pub use wgpu::{ ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, StoreOp, TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor, - TextureDimension, TextureFormat, TextureSampleType, TextureUsages, - TextureView as WgpuTextureView, TextureViewDescriptor, TextureViewDimension, VertexAttribute, - VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, - VertexStepMode, COPY_BUFFER_ALIGNMENT, + TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, + TextureSampleType, TextureUsages, TextureView as WgpuTextureView, TextureViewDescriptor, + TextureViewDimension, VertexAttribute, VertexBufferLayout as RawVertexBufferLayout, + VertexFormat, VertexState as RawVertexState, VertexStepMode, COPY_BUFFER_ALIGNMENT, }; pub use crate::mesh::VertexBufferLayout; diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 30f9a974b8..b76174cac3 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,12 +1,12 @@ use super::ShaderDefVal; use crate::mesh::VertexBufferLayout; -use crate::renderer::WgpuWrapper; use crate::{ define_atomic_id, render_resource::{BindGroupLayout, Shader}, }; use alloc::borrow::Cow; use bevy_asset::Handle; +use bevy_utils::WgpuWrapper; use core::ops::Deref; use wgpu::{ ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange, diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 70086cb6b2..416a83cd65 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -1,4 +1,3 @@ -use crate::renderer::WgpuWrapper; use crate::{ render_resource::*, renderer::{RenderAdapter, RenderDevice}, @@ -11,9 +10,10 @@ use bevy_ecs::{ resource::Resource, system::{Res, ResMut}, }; -use bevy_platform_support::collections::{hash_map::EntryRef, HashMap, HashSet}; +use bevy_platform::collections::{hash_map::EntryRef, HashMap, HashSet}; use bevy_tasks::Task; use bevy_utils::default; +use bevy_utils::WgpuWrapper; use core::{future::Future, hash::Hash, mem, ops::Deref}; use naga::valid::Capabilities; use std::sync::{Mutex, PoisonError}; @@ -80,6 +80,10 @@ pub struct CachedPipeline { } /// State of a cached pipeline inserted into a [`PipelineCache`]. +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] #[derive(Debug)] pub enum CachedPipelineState { /// The pipeline GPU object is queued for creation. @@ -135,7 +139,7 @@ struct ShaderCache { composer: naga_oil::compose::Composer, } -#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq, Debug, Hash)] pub enum ShaderDefVal { Bool(String, bool), Int(String, i32), @@ -189,33 +193,42 @@ impl ShaderCache { } } + #[expect( + clippy::result_large_err, + reason = "See https://github.com/bevyengine/bevy/issues/19220" + )] fn add_import_to_composer( composer: &mut naga_oil::compose::Composer, import_path_shaders: &HashMap>, shaders: &HashMap, Shader>, import: &ShaderImport, ) -> Result<(), PipelineCacheError> { - if !composer.contains_module(&import.module_name()) { - if let Some(shader_handle) = import_path_shaders.get(import) { - if let Some(shader) = shaders.get(shader_handle) { - for import in &shader.imports { - Self::add_import_to_composer( - composer, - import_path_shaders, - shaders, - import, - )?; - } - - composer.add_composable_module(shader.into())?; - } - } - // if we fail to add a module the composer will tell us what is missing + // Early out if we've already imported this module + if composer.contains_module(&import.module_name()) { + return Ok(()); } + // Check if the import is available (this handles the recursive import case) + let shader = import_path_shaders + .get(import) + .and_then(|handle| shaders.get(handle)) + .ok_or(PipelineCacheError::ShaderImportNotYetAvailable)?; + + // Recurse down to ensure all import dependencies are met + for import in &shader.imports { + Self::add_import_to_composer(composer, import_path_shaders, shaders, import)?; + } + + composer.add_composable_module(shader.into())?; + // if we fail to add a module the composer will tell us what is missing + Ok(()) } + #[expect( + clippy::result_large_err, + reason = "See https://github.com/bevyengine/bevy/issues/19220" + )] fn get( &mut self, render_device: &RenderDevice, @@ -555,13 +568,13 @@ impl LayoutCache { /// The cache stores existing render and compute pipelines allocated on the GPU, as well as /// pending creation. Pipelines inserted into the cache are identified by a unique ID, which /// can be used to retrieve the actual GPU object once it's ready. The creation of the GPU -/// pipeline object is deferred to the [`RenderSet::Render`] step, just before the render +/// pipeline object is deferred to the [`RenderSystems::Render`] step, just before the render /// graph starts being processed, as this requires access to the GPU. /// /// Note that the cache does not perform automatic deduplication of identical pipelines. It is /// up to the user not to insert the same pipeline twice to avoid wasting GPU resources. /// -/// [`RenderSet::Render`]: crate::RenderSet::Render +/// [`RenderSystems::Render`]: crate::RenderSystems::Render #[derive(Resource)] pub struct PipelineCache { layout_cache: Arc>, @@ -608,7 +621,10 @@ impl PipelineCache { /// See [`PipelineCache::queue_render_pipeline()`]. #[inline] pub fn get_render_pipeline_state(&self, id: CachedRenderPipelineId) -> &CachedPipelineState { - &self.pipelines[id.0].state + // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines` + self.pipelines + .get(id.0) + .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state) } /// Get the state of a cached compute pipeline. @@ -616,12 +632,18 @@ impl PipelineCache { /// See [`PipelineCache::queue_compute_pipeline()`]. #[inline] pub fn get_compute_pipeline_state(&self, id: CachedComputePipelineId) -> &CachedPipelineState { - &self.pipelines[id.0].state + // If the pipeline id isn't in `pipelines`, it's queued in `new_pipelines` + self.pipelines + .get(id.0) + .map_or(&CachedPipelineState::Queued, |pipeline| &pipeline.state) } /// Get the render pipeline descriptor a cached render pipeline was inserted from. /// /// See [`PipelineCache::queue_render_pipeline()`]. + /// + /// **Note**: Be careful calling this method. It will panic if called with a pipeline that + /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`]. #[inline] pub fn get_render_pipeline_descriptor( &self, @@ -636,6 +658,9 @@ impl PipelineCache { /// Get the compute pipeline descriptor a cached render pipeline was inserted from. /// /// See [`PipelineCache::queue_compute_pipeline()`]. + /// + /// **Note**: Be careful calling this method. It will panic if called with a pipeline that + /// has been queued but has not yet been processed by [`PipelineCache::process_queue()`]. #[inline] pub fn get_compute_pipeline_descriptor( &self, @@ -657,7 +682,7 @@ impl PipelineCache { #[inline] pub fn get_render_pipeline(&self, id: CachedRenderPipelineId) -> Option<&RenderPipeline> { if let CachedPipelineState::Ok(Pipeline::RenderPipeline(pipeline)) = - &self.pipelines[id.0].state + &self.pipelines.get(id.0)?.state { Some(pipeline) } else { @@ -691,7 +716,7 @@ impl PipelineCache { #[inline] pub fn get_compute_pipeline(&self, id: CachedComputePipelineId) -> Option<&ComputePipeline> { if let CachedPipelineState::Ok(Pipeline::ComputePipeline(pipeline)) = - &self.pipelines[id.0].state + &self.pipelines.get(id.0)?.state { Some(pipeline) } else { @@ -947,10 +972,10 @@ impl PipelineCache { /// Process the pipeline queue and create all pending pipelines if possible. /// - /// This is generally called automatically during the [`RenderSet::Render`] step, but can + /// This is generally called automatically during the [`RenderSystems::Render`] step, but can /// be called manually to force creation at a different time. /// - /// [`RenderSet::Render`]: crate::RenderSet::Render + /// [`RenderSystems::Render`]: crate::RenderSystems::Render pub fn process_queue(&mut self) { let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines); let mut pipelines = mem::take(&mut self.pipelines); @@ -1078,6 +1103,10 @@ fn create_pipeline_task( target_os = "macos", not(feature = "multi_threaded") ))] +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] fn create_pipeline_task( task: impl Future> + Send + 'static, _sync: bool, @@ -1089,6 +1118,10 @@ fn create_pipeline_task( } /// Type of error returned by a [`PipelineCache`] when the creation of a GPU pipeline object failed. +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] #[derive(Error, Debug)] pub enum PipelineCacheError { #[error( diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 67f737a6a4..e017242ea0 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -6,7 +6,7 @@ use crate::{ }, }; use bevy_ecs::resource::Resource; -use bevy_platform_support::{ +use bevy_platform::{ collections::{ hash_map::{Entry, RawEntryMut, VacantEntry}, HashMap, diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 62849a2f28..ff8430b951 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -163,26 +163,8 @@ impl Shader { match import_path { ShaderImport::AssetPath(asset_path) => { - let asset_path = std::path::PathBuf::from(&asset_path); - - // Get the base path and canonicalize it to match the format of the asset path - let mut base_path = bevy_asset::io::file::FileAssetReader::get_base_path(); - base_path.push("assets"); - let base_path = base_path.canonicalize().unwrap_or(base_path); - - // Try to make the path relative to the base path - let relative_path = match asset_path.canonicalize() { - Ok(canonical_asset_path) => { - match canonical_asset_path.strip_prefix(&base_path) { - Ok(rel_path) => rel_path.to_path_buf(), - Err(_) => canonical_asset_path, - } - } - Err(_) => asset_path, - }; - // Create the shader import path - always starting with "/" - let shader_path = std::path::Path::new("/").join(&relative_path); + let shader_path = std::path::Path::new("/").join(&asset_path); // Convert to a string with forward slashes and without extension let import_path_str = shader_path @@ -342,14 +324,21 @@ pub enum ShaderLoaderError { Parse(#[from] alloc::string::FromUtf8Error), } +/// Settings for loading shaders. +#[derive(serde::Serialize, serde::Deserialize, Debug, Default)] +pub struct ShaderSettings { + /// The `#define` specified for this shader. + pub shader_defs: Vec, +} + impl AssetLoader for ShaderLoader { type Asset = Shader; - type Settings = (); + type Settings = ShaderSettings; type Error = ShaderLoaderError; async fn load( &self, reader: &mut dyn Reader, - _settings: &Self::Settings, + settings: &Self::Settings, load_context: &mut LoadContext<'_>, ) -> Result { let ext = load_context.path().extension().unwrap().to_str().unwrap(); @@ -359,9 +348,19 @@ impl AssetLoader for ShaderLoader { let path = path.replace(std::path::MAIN_SEPARATOR, "/"); let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; + if ext != "wgsl" && !settings.shader_defs.is_empty() { + tracing::warn!( + "Tried to load a non-wgsl shader with shader defs, this isn't supported: \ + The shader defs will be ignored." + ); + } let mut shader = match ext { "spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()), - "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path), + "wgsl" => Shader::from_wgsl_with_defs( + String::from_utf8(bytes)?, + path, + settings.shader_defs.clone(), + ), "vert" => Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path), "frag" => { Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path) diff --git a/crates/bevy_render/src/render_resource/texture.rs b/crates/bevy_render/src/render_resource/texture.rs index f975fc18f3..c96da8a1be 100644 --- a/crates/bevy_render/src/render_resource/texture.rs +++ b/crates/bevy_render/src/render_resource/texture.rs @@ -1,7 +1,7 @@ use crate::define_atomic_id; -use crate::renderer::WgpuWrapper; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::resource::Resource; +use bevy_utils::WgpuWrapper; use core::ops::Deref; define_atomic_id!(TextureId); diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs index cc03374ea4..39f05ca6a8 100644 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ b/crates/bevy_render/src/renderer/graph_runner.rs @@ -1,5 +1,5 @@ use bevy_ecs::{prelude::Entity, world::World}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; #[cfg(feature = "trace")] use tracing::info_span; @@ -87,10 +87,10 @@ impl RenderGraphRunner { finalizer(render_context.command_encoder()); let (render_device, mut diagnostics_recorder) = { + let (commands, render_device, diagnostics_recorder) = render_context.finish(); + #[cfg(feature = "trace")] let _span = info_span!("submit_graph_commands").entered(); - - let (commands, render_device, diagnostics_recorder) = render_context.finish(); queue.submit(commands); (render_device, diagnostics_recorder) diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 32fe441f4d..81c693b01f 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -4,6 +4,7 @@ mod render_device; use bevy_derive::{Deref, DerefMut}; #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] use bevy_tasks::ComputeTaskPool; +use bevy_utils::WgpuWrapper; pub use graph_runner::*; pub use render_device::*; use tracing::{error, info, info_span, warn}; @@ -18,7 +19,7 @@ use crate::{ }; use alloc::sync::Arc; use bevy_ecs::{prelude::*, system::SystemState}; -use bevy_platform_support::time::Instant; +use bevy_platform::time::Instant; use bevy_time::TimeSender; use wgpu::{ Adapter, AdapterInfo, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue, @@ -120,46 +121,6 @@ pub fn render_system(world: &mut World, state: &mut SystemState(T); -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -#[derive(Debug, Clone, Deref, DerefMut)] -pub struct WgpuWrapper(send_wrapper::SendWrapper); - -// SAFETY: SendWrapper is always Send + Sync. -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -unsafe impl Send for WgpuWrapper {} -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -unsafe impl Sync for WgpuWrapper {} - -#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] -impl WgpuWrapper { - pub fn new(t: T) -> Self { - Self(t) - } - - pub fn into_inner(self) -> T { - self.0 - } -} - -#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] -impl WgpuWrapper { - pub fn new(t: T) -> Self { - Self(send_wrapper::SendWrapper::new(t)) - } - - pub fn into_inner(self) -> T { - self.0.take() - } -} - /// This queue is used to enqueue tasks for the GPU to execute asynchronously. #[derive(Resource, Clone, Deref, DerefMut)] pub struct RenderQueue(pub Arc>); @@ -498,6 +459,10 @@ impl<'w> RenderContext<'w> { let mut command_buffers = Vec::with_capacity(self.command_buffer_queue.len()); + #[cfg(feature = "trace")] + let _command_buffer_generation_tasks_span = + info_span!("command_buffer_generation_tasks").entered(); + #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] { let mut task_based_command_buffers = ComputeTaskPool::get().scope(|task_pool| { @@ -537,6 +502,9 @@ impl<'w> RenderContext<'w> { } } + #[cfg(feature = "trace")] + drop(_command_buffer_generation_tasks_span); + command_buffers.sort_unstable_by_key(|(i, _)| *i); let mut command_buffers = command_buffers diff --git a/crates/bevy_render/src/renderer/render_device.rs b/crates/bevy_render/src/renderer/render_device.rs index d33139745b..31f47e5740 100644 --- a/crates/bevy_render/src/renderer/render_device.rs +++ b/crates/bevy_render/src/renderer/render_device.rs @@ -3,8 +3,8 @@ use crate::render_resource::{ BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor, RenderPipeline, Sampler, Texture, }; -use crate::WgpuWrapper; use bevy_ecs::resource::Resource; +use bevy_utils::WgpuWrapper; use wgpu::{ util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BufferAsyncError, BufferBindingType, MaintainResult, diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index d4eb9b7680..ab50fc81b1 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -152,6 +152,10 @@ pub struct RenderResources( ); /// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin). +#[expect( + clippy::large_enum_variant, + reason = "See https://github.com/bevyengine/bevy/issues/19220" +)] pub enum RenderCreation { /// Allows renderer resource initialization to happen outside of the rendering plugin. Manual(RenderResources), diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 4f21e22bf9..f6c2f87593 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -3,7 +3,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHash; use bevy_ecs::{ component::Component, - entity::{Entity, EntityBorrow, TrustedEntityBorrow}, + entity::{ContainsEntity, Entity, EntityEquivalent}, observer::Trigger, query::With, reflect::ReflectComponent, @@ -11,7 +11,7 @@ use bevy_ecs::{ system::{Local, Query, ResMut, SystemState}, world::{Mut, OnAdd, OnRemove, World}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world. @@ -126,7 +126,9 @@ pub struct SyncToRenderWorld; /// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity. /// /// Can also be used as a newtype wrapper for render world entities. -#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq)] +#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)] +#[component(clone_behavior = Ignore)] +#[reflect(Component, Clone)] pub struct RenderEntity(Entity); impl RenderEntity { #[inline] @@ -141,19 +143,20 @@ impl From for RenderEntity { } } -impl EntityBorrow for RenderEntity { +impl ContainsEntity for RenderEntity { fn entity(&self) -> Entity { self.id() } } // SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl TrustedEntityBorrow for RenderEntity {} +unsafe impl EntityEquivalent for RenderEntity {} /// Component added on the render world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. -#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)] +#[reflect(Component, Clone)] pub struct MainEntity(Entity); impl MainEntity { #[inline] @@ -168,14 +171,14 @@ impl From for MainEntity { } } -impl EntityBorrow for MainEntity { +impl ContainsEntity for MainEntity { fn entity(&self) -> Entity { self.id() } } // SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl TrustedEntityBorrow for MainEntity {} +unsafe impl EntityEquivalent for MainEntity {} /// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. pub type MainEntityHashMap = HashMap; diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 277f9d477f..18c83414bd 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ system::SystemParam, }; use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; /// A [`RenderApp`](crate::RenderApp) resource that contains the default "fallback image", /// which can be used in situations where an image was not explicitly defined. The most common diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 6955de7ff4..6c2dc67aae 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -8,18 +8,22 @@ pub use crate::render_resource::DefaultImageSampler; use bevy_image::CompressedImageSaver; #[cfg(feature = "hdr")] use bevy_image::HdrTextureLoader; -use bevy_image::{CompressedImageFormats, Image, ImageLoader, ImageSamplerDescriptor}; +use bevy_image::{ + CompressedImageFormatSupport, CompressedImageFormats, Image, ImageLoader, + ImageSamplerDescriptor, +}; pub use fallback_image::*; pub use gpu_image::*; pub use texture_attachment::*; pub use texture_cache::*; use crate::{ - render_asset::RenderAssetPlugin, renderer::RenderDevice, Render, RenderApp, RenderSet, + render_asset::RenderAssetPlugin, renderer::RenderDevice, Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; use bevy_asset::{weak_handle, AssetApp, Assets, Handle}; use bevy_ecs::prelude::*; +use tracing::warn; /// A handle to a 1 x 1 transparent white image. /// @@ -100,7 +104,7 @@ impl Plugin for ImagePlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::().add_systems( Render, - update_texture_cache_system.in_set(RenderSet::Cleanup), + update_texture_cache_system.in_set(RenderSystems::Cleanup), ); } @@ -111,12 +115,16 @@ impl Plugin for ImagePlugin { fn finish(&self, app: &mut App) { if !ImageLoader::SUPPORTED_FORMATS.is_empty() { - let supported_compressed_formats = match app.world().get_resource::() { - Some(render_device) => { - CompressedImageFormats::from_features(render_device.features()) - } - None => CompressedImageFormats::NONE, + let supported_compressed_formats = if let Some(resource) = + app.world().get_resource::() + { + resource.0 + } else { + warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \ + RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend."); + CompressedImageFormats::NONE }; + app.register_asset_loader(ImageLoader::new(supported_compressed_formats)); } diff --git a/crates/bevy_render/src/texture/texture_cache.rs b/crates/bevy_render/src/texture/texture_cache.rs index 8a3fb01010..ca1ef9b31b 100644 --- a/crates/bevy_render/src/texture/texture_cache.rs +++ b/crates/bevy_render/src/texture/texture_cache.rs @@ -3,7 +3,7 @@ use crate::{ renderer::RenderDevice, }; use bevy_ecs::{prelude::ResMut, resource::Resource}; -use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use wgpu::{TextureDescriptor, TextureViewDescriptor}; /// The internal representation of a [`CachedTexture`] used to track whether it was recently used diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index dab77cb6d2..d25e8e7f49 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -1,7 +1,6 @@ pub mod visibility; pub mod window; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_diagnostic::FrameCount; pub use visibility::*; pub use window::*; @@ -13,7 +12,7 @@ use crate::{ }, experimental::occlusion_culling::OcclusionCulling, extract_component::ExtractComponentPlugin, - prelude::Shader, + load_shader_library, primitives::Frustum, render_asset::RenderAssets, render_phase::ViewRangefinder3d, @@ -24,7 +23,7 @@ use crate::{ CachedTexture, ColorAttachment, DepthAttachment, GpuImage, OutputColorAttachment, TextureCache, }, - Render, RenderApp, RenderSet, + Render, RenderApp, RenderSystems, }; use alloc::sync::Arc; use bevy_app::{App, Plugin}; @@ -33,7 +32,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_image::BevyDefault as _; use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; -use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render_macros::ExtractComponent; use bevy_transform::components::GlobalTransform; @@ -46,8 +45,6 @@ use wgpu::{ TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }; -pub const VIEW_TYPE_HANDLE: Handle = weak_handle!("7234423c-38bb-411c-acec-f67730f6db5b"); - /// The matrix that converts from the RGB to the LMS color space. /// /// To derive this, first we convert from RGB to [CIE 1931 XYZ]: @@ -101,7 +98,7 @@ pub struct ViewPlugin; impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { - load_internal_asset!(app, VIEW_TYPE_HANDLE, "view.wgsl", Shader::from_wgsl); + load_shader_library!(app, "view.wgsl"); app.register_type::() .register_type::() @@ -114,6 +111,7 @@ impl Plugin for ViewPlugin { .register_type::() // NOTE: windows.is_changed() handles cases where a window was resized .add_plugins(( + ExtractComponentPlugin::::default(), ExtractComponentPlugin::::default(), ExtractComponentPlugin::::default(), VisibilityPlugin, @@ -126,18 +124,18 @@ impl Plugin for ViewPlugin { ( // `TextureView`s need to be dropped before reconfiguring window surfaces. clear_view_attachments - .in_set(RenderSet::ManageViews) + .in_set(RenderSystems::ManageViews) .before(create_surfaces), prepare_view_attachments - .in_set(RenderSet::ManageViews) + .in_set(RenderSystems::ManageViews) .before(prepare_view_targets) .after(prepare_windows), prepare_view_targets - .in_set(RenderSet::ManageViews) + .in_set(RenderSystems::ManageViews) .after(prepare_windows) .after(crate::render_asset::prepare_assets::) .ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target` - prepare_view_uniforms.in_set(RenderSet::PrepareResources), + prepare_view_uniforms.in_set(RenderSystems::PrepareResources), ), ); } @@ -199,6 +197,16 @@ impl Msaa { } } +/// If this component is added to a camera, the camera will use an intermediate "high dynamic range" render texture. +/// This allows rendering with a wider range of lighting values. However, this does *not* affect +/// whether the camera will render with hdr display output (which bevy does not support currently) +/// and only affects the intermediate render texture. +#[derive( + Component, Default, Copy, Clone, ExtractComponent, Reflect, PartialEq, Eq, Hash, Debug, +)] +#[reflect(Component, Default, PartialEq, Hash, Debug)] +pub struct Hdr; + /// An identifier for a view that is stable across frames. /// /// We can't use [`Entity`] for this because render world entities aren't @@ -262,34 +270,36 @@ impl RetainedViewEntity { pub struct ExtractedView { /// The entity in the main world corresponding to this render world view. pub retained_view_entity: RetainedViewEntity, - /// Typically a right-handed projection matrix, one of either: + /// Typically a column-major right-handed projection matrix, one of either: /// /// Perspective (infinite reverse z) /// ```text /// f = 1 / tan(fov_y_radians / 2) /// - /// ⎡ f / aspect 0 0 0 ⎤ - /// ⎢ 0 f 0 0 ⎥ - /// ⎢ 0 0 0 -1 ⎥ - /// ⎣ 0 0 near 0 ⎦ + /// ⎡ f / aspect 0 0 0 ⎤ + /// ⎢ 0 f 0 0 ⎥ + /// ⎢ 0 0 0 near ⎥ + /// ⎣ 0 0 -1 0 ⎦ /// ``` /// /// Orthographic /// ```text /// w = right - left /// h = top - bottom - /// d = near - far + /// d = far - near /// cw = -right - left /// ch = -top - bottom /// - /// ⎡ 2 / w 0 0 0 ⎤ - /// ⎢ 0 2 / h 0 0 ⎥ - /// ⎢ 0 0 1 / d 0 ⎥ - /// ⎣ cw / w ch / h near / d 1 ⎦ + /// ⎡ 2 / w 0 0 cw / w ⎤ + /// ⎢ 0 2 / h 0 ch / h ⎥ + /// ⎢ 0 0 1 / d far / d ⎥ + /// ⎣ 0 0 0 1 ⎦ /// ``` /// /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic /// + /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` + /// /// Custom projections are also possible however. pub clip_from_view: Mat4, pub world_from_view: GlobalTransform, @@ -529,34 +539,36 @@ pub struct ViewUniform { pub world_from_clip: Mat4, pub world_from_view: Mat4, pub view_from_world: Mat4, - /// Typically a right-handed projection matrix, one of either: + /// Typically a column-major right-handed projection matrix, one of either: /// /// Perspective (infinite reverse z) /// ```text /// f = 1 / tan(fov_y_radians / 2) /// - /// ⎡ f / aspect 0 0 0 ⎤ - /// ⎢ 0 f 0 0 ⎥ - /// ⎢ 0 0 0 -1 ⎥ - /// ⎣ 0 0 near 0 ⎦ + /// ⎡ f / aspect 0 0 0 ⎤ + /// ⎢ 0 f 0 0 ⎥ + /// ⎢ 0 0 0 near ⎥ + /// ⎣ 0 0 -1 0 ⎦ /// ``` /// /// Orthographic /// ```text /// w = right - left /// h = top - bottom - /// d = near - far + /// d = far - near /// cw = -right - left /// ch = -top - bottom /// - /// ⎡ 2 / w 0 0 0 ⎤ - /// ⎢ 0 2 / h 0 0 ⎥ - /// ⎢ 0 0 1 / d 0 ⎥ - /// ⎣ cw / w ch / h near / d 1 ⎦ + /// ⎡ 2 / w 0 0 cw / w ⎤ + /// ⎢ 0 2 / h 0 ch / h ⎥ + /// ⎢ 0 0 1 / d far / d ⎥ + /// ⎣ 0 0 0 1 ⎦ /// ``` /// /// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic /// + /// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` + /// /// Custom projections are also possible however. pub clip_from_view: Mat4, pub view_from_clip: Mat4, @@ -711,6 +723,9 @@ impl From for ColorGradingUniform { /// /// The vast majority of applications will not need to use this component, as it /// generally reduces rendering performance. +/// +/// Note: This component should only be added when initially spawning a camera. Adding +/// or removing after spawn can result in unspecified behavior. #[derive(Component, Default)] pub struct NoIndirectDrawing; diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index 317de2eb88..7b14bab9e1 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -19,33 +19,35 @@ struct View { world_from_clip: mat4x4, world_from_view: mat4x4, view_from_world: mat4x4, - // Typically a right-handed projection matrix, one of either: + // Typically a column-major right-handed projection matrix, one of either: // // Perspective (infinite reverse z) // ``` // f = 1 / tan(fov_y_radians / 2) // - // ⎡ f / aspect 0 0 0 ⎤ - // ⎢ 0 f 0 0 ⎥ - // ⎢ 0 0 0 -1 ⎥ - // ⎣ 0 0 near 0 ⎦ + // ⎡ f / aspect 0 0 0 ⎤ + // ⎢ 0 f 0 0 ⎥ + // ⎢ 0 0 0 near ⎥ + // ⎣ 0 0 -1 0 ⎦ // ``` // // Orthographic // ``` // w = right - left // h = top - bottom - // d = near - far + // d = far - near // cw = -right - left // ch = -top - bottom // - // ⎡ 2 / w 0 0 0 ⎤ - // ⎢ 0 2 / h 0 0 ⎥ - // ⎢ 0 0 1 / d 0 ⎥ - // ⎣ cw / w ch / h near / d 1 ⎦ + // ⎡ 2 / w 0 0 cw / w ⎤ + // ⎢ 0 2 / h 0 ch / h ⎥ + // ⎢ 0 0 1 / d far / d ⎥ + // ⎣ 0 0 0 1 ⎦ // ``` // // `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic + // + // Wgsl matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]` // // Custom projections are also possible however. clip_from_view: mat4x4, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index d72c52abf7..3a0772b687 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -4,7 +4,7 @@ mod render_layers; use core::any::TypeId; use bevy_ecs::component::HookContext; -use bevy_ecs::entity::hash_set::EntityHashSet; +use bevy_ecs::entity::EntityHashSet; use bevy_ecs::world::DeferredWorld; use derive_more::derive::{Deref, DerefMut}; pub use range::*; @@ -14,7 +14,7 @@ use bevy_app::{Plugin, PostUpdate}; use bevy_asset::Assets; use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_transform::{components::GlobalTransform, TransformSystem}; +use bevy_transform::{components::GlobalTransform, TransformSystems}; use bevy_utils::{Parallel, TypeIdMap}; use smallvec::SmallVec; @@ -342,7 +342,7 @@ impl Plugin for VisibilityPlugin { PostUpdate, (CalculateBounds, UpdateFrusta, VisibilityPropagate) .before(CheckVisibility) - .after(TransformSystem::TransformPropagate), + .after(TransformSystems::Propagate), ) .configure_sets( PostUpdate, @@ -411,7 +411,7 @@ fn visibility_propagate_system( Visibility::Hidden => false, // fall back to true if no parent is found or parent lacks components Visibility::Inherited => child_of - .and_then(|c| visibility_query.get(c.parent).ok()) + .and_then(|c| visibility_query.get(c.parent()).ok()) .is_none_or(|(_, x)| x.get()), }; let (_, mut inherited_visibility) = visibility_query @@ -786,9 +786,7 @@ mod test { .entity_mut(parent2) .insert(Visibility::Visible); // Simulate a change in the parent component - app.world_mut() - .entity_mut(child2) - .insert(ChildOf { parent: parent2 }); // example of changing parent + app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent // Run the system again to propagate changes app.update(); diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index 5a6728337b..2559d3b8d2 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -9,19 +9,19 @@ use core::{ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ component::Component, - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, query::{Changed, With}, reflect::ReflectComponent, removal_detection::RemovedComponents, resource::Resource, schedule::IntoScheduleConfigs as _, - system::{Query, Res, ResMut}, + system::{Local, Query, Res, ResMut}, }; use bevy_math::{vec4, FloatOrd, Vec4}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; use bevy_transform::components::GlobalTransform; -use bevy_utils::prelude::default; +use bevy_utils::{prelude::default, Parallel}; use nonmax::NonMaxU16; use wgpu::{BufferBindingType, BufferUsages}; @@ -32,7 +32,7 @@ use crate::{ primitives::Aabb, render_resource::BufferVec, renderer::{RenderDevice, RenderQueue}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; /// We need at least 4 storage buffer bindings available to enable the @@ -72,7 +72,7 @@ impl Plugin for VisibilityRangePlugin { .add_systems(ExtractSchedule, extract_visibility_ranges) .add_systems( Render, - write_render_visibility_ranges.in_set(RenderSet::PrepareResourcesFlush), + write_render_visibility_ranges.in_set(RenderSystems::PrepareResourcesFlush), ); } } @@ -385,7 +385,8 @@ impl VisibleEntityRanges { pub fn check_visibility_ranges( mut visible_entity_ranges: ResMut, view_query: Query<(Entity, &GlobalTransform), With>, - mut entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>, + mut par_local: Local>>, + entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>, ) { visible_entity_ranges.clear(); @@ -404,30 +405,34 @@ pub fn check_visibility_ranges( // Check each entity/view pair. Only consider entities with // [`VisibilityRange`] components. - for (entity, entity_transform, maybe_model_aabb, visibility_range) in entity_query.iter_mut() { - let mut visibility = 0; - for (view_index, &(_, view_position)) in views.iter().enumerate() { - // If instructed to use the AABB and the model has one, use its - // center as the model position. Otherwise, use the model's - // translation. - let model_position = match (visibility_range.use_aabb, maybe_model_aabb) { - (true, Some(model_aabb)) => entity_transform - .affine() - .transform_point3a(model_aabb.center), - _ => entity_transform.translation_vec3a(), - }; + entity_query.par_iter().for_each( + |(entity, entity_transform, maybe_model_aabb, visibility_range)| { + let mut visibility = 0; + for (view_index, &(_, view_position)) in views.iter().enumerate() { + // If instructed to use the AABB and the model has one, use its + // center as the model position. Otherwise, use the model's + // translation. + let model_position = match (visibility_range.use_aabb, maybe_model_aabb) { + (true, Some(model_aabb)) => entity_transform + .affine() + .transform_point3a(model_aabb.center), + _ => entity_transform.translation_vec3a(), + }; - if visibility_range.is_visible_at_all((view_position - model_position).length()) { - visibility |= 1 << view_index; + if visibility_range.is_visible_at_all((view_position - model_position).length()) { + visibility |= 1 << view_index; + } } - } - // Invisible entities have no entry at all in the hash map. This speeds - // up checks slightly in this common case. - if visibility != 0 { - visible_entity_ranges.entities.insert(entity, visibility); - } - } + // Invisible entities have no entry at all in the hash map. This speeds + // up checks slightly in this common case. + if visibility != 0 { + par_local.borrow_local_mut().push((entity, visibility)); + } + }, + ); + + visible_entity_ranges.entities.extend(par_local.drain()); } /// Extracts all [`VisibilityRange`] components from the main world to the diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index a5a58453e8..b39ecb215c 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -7,18 +7,14 @@ pub const DEFAULT_LAYERS: &RenderLayers = &RenderLayers::layer(0); /// An identifier for a rendering layer. pub type Layer = usize; -/// Describes which rendering layers an entity belongs to. +/// Defines which rendering layers an entity belongs to. /// -/// Cameras with this component will only render entities with intersecting -/// layers. +/// A camera renders an entity only when their render layers intersect. /// -/// Entities may belong to one or more layers, or no layer at all. +/// The [`Default`] instance of `RenderLayers` contains layer `0`, the first layer. Entities +/// without this component also belong to layer `0`. /// -/// The [`Default`] instance of `RenderLayers` contains layer `0`, the first layer. -/// -/// An entity with this component without any layers is invisible. -/// -/// Entities without this component belong to layer `0`. +/// An empty `RenderLayers` makes the entity invisible. #[derive(Component, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] #[reflect(Component, Default, PartialEq, Debug, Clone)] pub struct RenderLayers(SmallVec<[u64; INLINE_BLOCKS]>); diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 14a984ff39..657106d5a0 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -1,12 +1,13 @@ use crate::{ render_resource::{SurfaceTexture, TextureView}, renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, WgpuWrapper, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_app::{App, Plugin}; -use bevy_ecs::{entity::hash_map::EntityHashMap, prelude::*}; -use bevy_platform_support::collections::HashSet; +use bevy_ecs::{entity::EntityHashMap, prelude::*}; +use bevy_platform::collections::HashSet; use bevy_utils::default; +use bevy_utils::WgpuWrapper; use bevy_window::{ CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing, }; @@ -21,7 +22,7 @@ use wgpu::{ pub mod screenshot; -use screenshot::{ScreenshotPlugin, ScreenshotToScreenPipeline}; +use screenshot::ScreenshotPlugin; pub struct WindowRenderPlugin; @@ -40,13 +41,7 @@ impl Plugin for WindowRenderPlugin { .run_if(need_surface_configuration) .before(prepare_windows), ) - .add_systems(Render, prepare_windows.in_set(RenderSet::ManageViews)); - } - } - - fn finish(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::(); + .add_systems(Render, prepare_windows.in_set(RenderSystems::ManageViews)); } } } @@ -304,9 +299,7 @@ const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2; pub fn create_surfaces( // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, // which is necessary for some OS's - #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: Option< - NonSend, - >, + #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker, windows: Res, mut window_surfaces: ResMut, render_instance: Res, diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index d8a309036e..56d591a365 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -13,17 +13,17 @@ use crate::{ renderer::RenderDevice, texture::{GpuImage, OutputColorAttachment}, view::{prepare_view_attachments, prepare_view_targets, ViewTargetAttachments, WindowSurfaces}, - ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, + ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems, }; use alloc::{borrow::Cow, sync::Arc}; use bevy_app::{First, Plugin, Update}; -use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::hash_map::EntityHashMap, event::event_update_system, prelude::*, system::SystemState, + entity::EntityHashMap, event::event_update_system, prelude::*, system::SystemState, }; use bevy_image::{Image, TextureFormatPixelInfo}; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::Reflect; use bevy_tasks::AsyncComputeTaskPool; use bevy_utils::default; @@ -392,9 +392,6 @@ fn prepare_screenshot_state( pub struct ScreenshotPlugin; -const SCREENSHOT_SHADER_HANDLE: Handle = - weak_handle!("c31753d6-326a-47cb-a359-65c97a471fda"); - impl Plugin for ScreenshotPlugin { fn build(&self, app: &mut bevy_app::App) { app.add_systems( @@ -403,21 +400,16 @@ impl Plugin for ScreenshotPlugin { .after(event_update_system) .before(ApplyDeferred), ) - .add_systems(Update, trigger_screenshots) .register_type::() .register_type::(); - load_internal_asset!( - app, - SCREENSHOT_SHADER_HANDLE, - "screenshot.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "screenshot.wgsl"); } fn finish(&self, app: &mut bevy_app::App) { let (tx, rx) = std::sync::mpsc::channel(); - app.insert_resource(CapturedScreenshots(Arc::new(Mutex::new(rx)))); + app.add_systems(Update, trigger_screenshots) + .insert_resource(CapturedScreenshots(Arc::new(Mutex::new(rx)))); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -425,13 +417,14 @@ impl Plugin for ScreenshotPlugin { .init_resource::() .init_resource::() .init_resource::>() + .init_resource::() .add_systems(ExtractSchedule, extract_screenshots.ambiguous_with_all()) .add_systems( Render, prepare_screenshots .after(prepare_view_attachments) .before(prepare_view_targets) - .in_set(RenderSet::ManageViews), + .in_set(RenderSystems::ManageViews), ); } } @@ -440,6 +433,7 @@ impl Plugin for ScreenshotPlugin { #[derive(Resource)] pub struct ScreenshotToScreenPipeline { pub bind_group_layout: BindGroupLayout, + pub shader: Handle, } impl FromWorld for ScreenshotToScreenPipeline { @@ -454,7 +448,12 @@ impl FromWorld for ScreenshotToScreenPipeline { ), ); - Self { bind_group_layout } + let shader = load_embedded_asset!(render_world, "screenshot.wgsl"); + + Self { + bind_group_layout, + shader, + } } } @@ -469,7 +468,7 @@ impl SpecializedRenderPipeline for ScreenshotToScreenPipeline { buffers: vec![], shader_defs: vec![], entry_point: Cow::Borrowed("vs_main"), - shader: SCREENSHOT_SHADER_HANDLE, + shader: self.shader.clone(), }, primitive: wgpu::PrimitiveState { cull_mode: Some(wgpu::Face::Back), @@ -478,7 +477,7 @@ impl SpecializedRenderPipeline for ScreenshotToScreenPipeline { depth_stencil: None, multisample: Default::default(), fragment: Some(FragmentState { - shader: SCREENSHOT_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: Cow::Borrowed("fs_main"), shader_defs: vec![], targets: vec![Some(wgpu::ColorTargetState { diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index d7fb04aaba..3bb913c859 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -14,7 +14,7 @@ serialize = [ "dep:serde", "uuid/serde", "bevy_ecs/serialize", - "bevy_platform_support/serialize", + "bevy_platform/serialize", ] [dependencies] @@ -27,7 +27,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } @@ -43,7 +43,7 @@ uuid = { version = "1.13.1", default-features = false, features = ["js"] } [dev-dependencies] postcard = { version = "1.0", features = ["alloc"] } -bincode = "1.3" +bincode = { version = "2.0", features = ["serde"] } rmp-serde = "1.1" [lints] diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index bfe2466703..f0cf3960d6 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,20 +1,23 @@ -use crate::{ron, DynamicSceneBuilder, Scene, SceneSpawnError}; +use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource}; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, + entity::{Entity, EntityHashMap, SceneEntityMapper}, reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; -use bevy_reflect::{PartialReflect, TypePath, TypeRegistry}; +use bevy_reflect::{PartialReflect, TypePath}; use crate::reflect_utils::clone_reflect_value; -#[cfg(feature = "serialize")] -use crate::serde::SceneSerializer; use bevy_ecs::component::ComponentCloneBehavior; use bevy_ecs::relationship::RelationshipHookMode; + #[cfg(feature = "serialize")] -use serde::Serialize; +use { + crate::{ron, serde::SceneSerializer}, + bevy_reflect::TypeRegistry, + serde::Serialize, +}; /// A collection of serializable resources and dynamic entities. /// @@ -149,16 +152,22 @@ impl DynamicScene { // If this component references entities in the scene, update // them to the entities in the world. - if let Some(map_entities) = registration.data::() { - let mut resource = clone_reflect_value(resource.as_partial_reflect(), registration); + let mut cloned_resource; + let partial_reflect_resource = if let Some(map_entities) = + registration.data::() + { + cloned_resource = clone_reflect_value(resource.as_partial_reflect(), registration); SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(resource.as_partial_reflect_mut(), mapper); + map_entities.map_entities(cloned_resource.as_partial_reflect_mut(), mapper); }); - } + cloned_resource.as_partial_reflect() + } else { + resource.as_partial_reflect() + }; // If the world already contains an instance of the given resource // just apply the (possibly) new value, otherwise insert the resource - reflect_resource.apply_or_insert(world, resource.as_partial_reflect(), &type_registry); + reflect_resource.apply_or_insert(world, partial_reflect_resource, &type_registry); } Ok(()) @@ -208,24 +217,24 @@ where mod tests { use bevy_ecs::{ component::Component, - entity::{ - hash_map::EntityHashMap, Entity, EntityMapper, MapEntities, VisitEntities, - VisitEntitiesMut, - }, + entity::{Entity, EntityHashMap, EntityMapper, MapEntities}, hierarchy::ChildOf, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, resource::Resource, world::World, }; + use bevy_reflect::Reflect; use crate::dynamic_scene::DynamicScene; use crate::dynamic_scene_builder::DynamicSceneBuilder; - #[derive(Resource, Reflect, Debug, VisitEntities, VisitEntitiesMut)] + #[derive(Resource, Reflect, MapEntities, Debug)] #[reflect(Resource, MapEntities)] struct TestResource { + #[entities] entity_a: Entity, + #[entities] entity_b: Entity, } @@ -316,7 +325,7 @@ mod tests { .unwrap() .get::() .unwrap() - .parent, + .parent(), "something about reloading the scene is touching entities with the same scene Ids" ); assert_eq!( @@ -326,7 +335,7 @@ mod tests { .unwrap() .get::() .unwrap() - .parent, + .parent(), "something about reloading the scene is touching components not defined in the scene but on entities defined in the scene" ); assert_eq!( @@ -336,7 +345,7 @@ mod tests { .unwrap() .get::() .expect("something is wrong with this test, and the scene components don't have a parent/child relationship") - .parent, + .parent(), "something is wrong with this test or the code reloading scenes since the relationship between scene entities is broken" ); } @@ -354,7 +363,7 @@ mod tests { struct B(pub Entity); impl MapEntities for B { - fn map_entities(&mut self, entity_mapper: &mut M) { + fn map_entities(&mut self, entity_mapper: &mut E) { self.0 = entity_mapper.get_mapped(self.0); } } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 5deac09aaa..c9e594107e 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -509,10 +509,10 @@ mod tests { let mut entities = builder.build().entities.into_iter(); // Assert entities are ordered - assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap()); assert_eq!(entity_d, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap()); } #[test] @@ -539,7 +539,7 @@ mod tests { assert_eq!(scene.entities.len(), 2); let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity]; scene_entities.sort(); - assert_eq!(scene_entities, [entity_a_b, entity_a]); + assert_eq!(scene_entities, [entity_a, entity_a_b]); } #[test] @@ -621,9 +621,9 @@ mod tests { .build(); assert_eq!(scene.entities.len(), 3); - assert!(scene.entities[0].components[0].represents::()); + assert!(scene.entities[2].components[0].represents::()); assert!(scene.entities[1].components[0].represents::()); - assert_eq!(scene.entities[2].components.len(), 0); + assert_eq!(scene.entities[0].components.len(), 0); } #[test] diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 7a9526c0f4..a507a58aaf 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -27,7 +27,6 @@ pub mod serde; /// Rusty Object Notation, a crate used to serialize and deserialize bevy scenes. pub use bevy_asset::ron; -use bevy_ecs::schedule::IntoScheduleConfigs; pub use components::*; pub use dynamic_scene::*; pub use dynamic_scene_builder::*; @@ -48,7 +47,9 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_asset::AssetApp; + +#[cfg(feature = "serialize")] +use {bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs}; /// Plugin that provides scene functionality to an [`App`]. #[derive(Default)] diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 104a129f79..1d684c9dac 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -5,7 +5,7 @@ use crate::{DynamicScene, SceneSpawnError}; use bevy_asset::Asset; use bevy_ecs::{ component::ComponentCloneBehavior, - entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, + entity::{Entity, EntityHashMap, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, relationship::RelationshipHookMode, diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index 732a1e5f15..a3154c37e7 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -1,4 +1,4 @@ -use bevy_platform_support::collections::{hash_set::IntoIter, HashSet}; +use bevy_platform::collections::{hash_set::IntoIter, HashSet}; use core::any::{Any, TypeId}; /// A filter used to control which types can be added to a [`DynamicScene`]. diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index 481b7ebc04..d74dff84f5 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -1,21 +1,27 @@ -#[cfg(feature = "serialize")] -use crate::serde::SceneDeserializer; -use crate::{ron, DynamicScene}; -use bevy_asset::{io::Reader, AssetLoader, LoadContext}; +use crate::ron; use bevy_ecs::{ reflect::AppTypeRegistry, world::{FromWorld, World}, }; use bevy_reflect::TypeRegistryArc; -#[cfg(feature = "serialize")] -use serde::de::DeserializeSeed; use thiserror::Error; +#[cfg(feature = "serialize")] +use { + crate::{serde::SceneDeserializer, DynamicScene}, + bevy_asset::{io::Reader, AssetLoader, LoadContext}, + serde::de::DeserializeSeed, +}; + /// Asset loader for a Bevy dynamic scene (`.scn` / `.scn.ron`). /// /// The loader handles assets serialized with [`DynamicScene::serialize`]. #[derive(Debug)] pub struct SceneLoader { + #[cfg_attr( + not(feature = "serialize"), + expect(dead_code, reason = "only used with `serialize` feature") + )] type_registry: TypeRegistryArc, } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 4acfac93a9..3bf8ca9f64 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -1,14 +1,14 @@ use crate::{DynamicScene, Scene}; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, event::{Event, EventCursor, Events}, hierarchy::ChildOf, reflect::AppTypeRegistry, resource::Resource, world::{Mut, World}, }; -use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::Reflect; use thiserror::Error; use uuid::Uuid; @@ -331,10 +331,7 @@ impl SceneSpawner { Ok(_) => { self.spawned_instances .insert(instance_id, InstanceInfo { entity_map }); - let spawned = self - .spawned_dynamic_scenes - .entry(handle.id()) - .or_insert_with(HashSet::default); + let spawned = self.spawned_dynamic_scenes.entry(handle.id()).or_default(); spawned.insert(instance_id); // Scenes with parents need more setup before they are ready. @@ -892,30 +889,15 @@ mod tests { // Spawn entities with different parent first before parenting them to the actual root, allowing us // to decouple child order from archetype-creation-order let child1 = scene_world - .spawn(( - ChildOf { - parent: temporary_root, - }, - ComponentA { x: 1.0, y: 1.0 }, - )) + .spawn((ChildOf(temporary_root), ComponentA { x: 1.0, y: 1.0 })) .id(); let child2 = scene_world - .spawn(( - ChildOf { - parent: temporary_root, - }, - ComponentA { x: 2.0, y: 2.0 }, - )) + .spawn((ChildOf(temporary_root), ComponentA { x: 2.0, y: 2.0 })) .id(); // the "first" child is intentionally spawned with a different component to force it into a "newer" archetype, // meaning it will be iterated later in the spawn code. let child0 = scene_world - .spawn(( - ChildOf { - parent: temporary_root, - }, - ComponentF, - )) + .spawn((ChildOf(temporary_root), ComponentF)) .id(); scene_world @@ -930,7 +912,7 @@ mod tests { app.update(); let world = app.world_mut(); - let spawned_root = world.entity(spawned).get::().unwrap()[0]; + let spawned_root = world.entity(spawned).get::().unwrap()[1]; let children = world.entity(spawned_root).get::().unwrap(); assert_eq!(children.len(), 3); assert!(world.entity(children[0]).get::().is_some()); diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 103e48e50a..cb8206d3dd 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -2,7 +2,7 @@ use crate::{DynamicEntity, DynamicScene}; use bevy_ecs::entity::Entity; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::{ serde::{ ReflectDeserializer, TypeRegistrationDeserializer, TypedReflectDeserializer, @@ -515,14 +515,13 @@ mod tests { DynamicScene, DynamicSceneBuilder, }; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity}, + entity::{Entity, EntityHashMap}, prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, reflect::AppTypeRegistry, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; - use bincode::Options; use serde::{de::DeserializeSeed, Deserialize, Serialize}; use std::io::BufReader; @@ -639,24 +638,24 @@ mod tests { ), }, entities: { - 4294967296: ( - components: { - "bevy_scene::serde::tests::Foo": (123), - }, - ), - 4294967297: ( - components: { - "bevy_scene::serde::tests::Bar": (345), - "bevy_scene::serde::tests::Foo": (123), - }, - ), - 4294967298: ( + 4294967293: ( components: { "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Foo": (123), }, ), + 4294967294: ( + components: { + "bevy_scene::serde::tests::Bar": (345), + "bevy_scene::serde::tests::Foo": (123), + }, + ), + 4294967295: ( + components: { + "bevy_scene::serde::tests::Foo": (123), + }, + ), }, )"#; let output = scene @@ -676,18 +675,18 @@ mod tests { ), }, entities: { - 4294967296: ( + 8589934591: ( components: { "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967297: ( + 8589934590: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), }, ), - 4294967298: ( + 8589934589: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), @@ -816,7 +815,7 @@ mod tests { assert_eq!( vec![ - 0, 1, 128, 128, 128, 128, 16, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, + 0, 1, 255, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 @@ -857,8 +856,8 @@ mod tests { assert_eq!( vec![ - 146, 128, 129, 207, 0, 0, 0, 1, 0, 0, 0, 0, 145, 129, 217, 37, 98, 101, 118, 121, - 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, + 146, 128, 129, 206, 255, 255, 255, 255, 145, 129, 217, 37, 98, 101, 118, 121, 95, + 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 @@ -894,17 +893,19 @@ mod tests { let scene = DynamicScene::from_world(&world); + let config = bincode::config::standard().with_fixed_int_encoding(); let scene_serializer = SceneSerializer::new(&scene, registry); - let serialized_scene = bincode::serialize(&scene_serializer).unwrap(); + let serialized_scene = bincode::serde::encode_to_vec(&scene_serializer, config).unwrap(); assert_eq!( vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, - 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, - 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, - 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, - 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, + 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, + 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, + 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, + 100, 33 ], serialized_scene ); @@ -913,10 +914,9 @@ mod tests { type_registry: registry, }; - let deserialized_scene = bincode::DefaultOptions::new() - .with_fixint_encoding() - .deserialize_seed(scene_deserializer, &serialized_scene) - .unwrap(); + let (deserialized_scene, _read_bytes) = + bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config) + .unwrap(); assert_eq!(1, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 67c28f0ec8..8fa5bae2cc 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -29,7 +29,7 @@ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev", optional = true } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 1107e6c38e..771eb473fd 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -21,6 +21,11 @@ mod texture_slice; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { + #[cfg(feature = "bevy_sprite_picking_backend")] + #[doc(hidden)] + pub use crate::picking_backend::{ + SpritePickingCamera, SpritePickingMode, SpritePickingPlugin, SpritePickingSettings, + }; #[doc(hidden)] pub use crate::{ sprite::{Sprite, SpriteImageMode}, @@ -37,70 +42,41 @@ pub use sprite::*; pub use texture_slice::*; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, AssetEvents, Assets, Handle}; +use bevy_asset::{embedded_asset, AssetEventSystems, Assets}; use bevy_core_pipeline::core_2d::{AlphaMask2d, Opaque2d, Transparent2d}; use bevy_ecs::prelude::*; use bevy_image::{prelude::*, TextureAtlasPlugin}; use bevy_render::{ batching::sort_binned_render_phase, + load_shader_library, mesh::{Mesh, Mesh2d, MeshAabb}, primitives::Aabb, render_phase::AddRenderCommand, - render_resource::{Shader, SpecializedRenderPipelines}, + render_resource::SpecializedRenderPipelines, view::{NoFrustumCulling, VisibilitySystems}, - ExtractSchedule, Render, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSystems, }; /// Adds support for 2D sprite rendering. -pub struct SpritePlugin { - /// Whether to add the sprite picking backend to the app. - #[cfg(feature = "bevy_sprite_picking_backend")] - pub add_picking: bool, -} - -#[expect( - clippy::allow_attributes, - reason = "clippy::derivable_impls is not always linted" -)] -#[allow( - clippy::derivable_impls, - reason = "Known false positive with clippy: " -)] -impl Default for SpritePlugin { - fn default() -> Self { - Self { - #[cfg(feature = "bevy_sprite_picking_backend")] - add_picking: true, - } - } -} - -pub const SPRITE_SHADER_HANDLE: Handle = - weak_handle!("ed996613-54c0-49bd-81be-1c2d1a0d03c2"); -pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle = - weak_handle!("43947210-8df6-459a-8f2a-12f350d174cc"); +#[derive(Default)] +pub struct SpritePlugin; /// System set for sprite rendering. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum SpriteSystem { +pub enum SpriteSystems { ExtractSprites, ComputeSlices, } +/// Deprecated alias for [`SpriteSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `SpriteSystems`.")] +pub type SpriteSystem = SpriteSystems; + impl Plugin for SpritePlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - SPRITE_SHADER_HANDLE, - "render/sprite.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - SPRITE_VIEW_BINDINGS_SHADER_HANDLE, - "render/sprite_view_bindings.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "render/sprite_view_bindings.wgsl"); + + embedded_asset!(app, "render/sprite.wgsl"); if !app.is_plugin_added::() { app.add_plugins(TextureAtlasPlugin); @@ -117,17 +93,15 @@ impl Plugin for SpritePlugin { ( calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds), ( - compute_slices_on_asset_event.before(AssetEvents), + compute_slices_on_asset_event.before(AssetEventSystems), compute_slices_on_sprite_change, ) - .in_set(SpriteSystem::ComputeSlices), + .in_set(SpriteSystems::ComputeSlices), ), ); #[cfg(feature = "bevy_sprite_picking_backend")] - if self.add_picking { - app.add_plugins(SpritePickingPlugin); - } + app.add_plugins(SpritePickingPlugin); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -135,12 +109,13 @@ impl Plugin for SpritePlugin { .init_resource::>() .init_resource::() .init_resource::() + .init_resource::() .init_resource::() .add_render_command::() .add_systems( ExtractSchedule, ( - extract_sprites.in_set(SpriteSystem::ExtractSprites), + extract_sprites.in_set(SpriteSystems::ExtractSprites), extract_sprite_events, ), ) @@ -148,12 +123,12 @@ impl Plugin for SpritePlugin { Render, ( queue_sprites - .in_set(RenderSet::Queue) + .in_set(RenderSystems::Queue) .ambiguous_with(queue_material2d_meshes::), - prepare_sprite_image_bind_groups.in_set(RenderSet::PrepareBindGroups), - prepare_sprite_view_bind_groups.in_set(RenderSet::PrepareBindGroups), - sort_binned_render_phase::.in_set(RenderSet::PhaseSort), - sort_binned_render_phase::.in_set(RenderSet::PhaseSort), + prepare_sprite_image_bind_groups.in_set(RenderSystems::PrepareBindGroups), + prepare_sprite_view_bind_groups.in_set(RenderSystems::PrepareBindGroups), + sort_binned_render_phase::.in_set(RenderSystems::PhaseSort), + sort_binned_render_phase::.in_set(RenderSystems::PhaseSort), ), ); }; @@ -181,9 +156,9 @@ pub fn calculate_bounds_2d( atlases: Res>, meshes_without_aabb: Query<(Entity, &Mesh2d), (Without, Without)>, sprites_to_recalculate_aabb: Query< - (Entity, &Sprite), + (Entity, &Sprite, &Anchor), ( - Or<(Without, Changed)>, + Or<(Without, Changed, Changed)>, Without, ), >, @@ -195,7 +170,7 @@ pub fn calculate_bounds_2d( } } } - for (entity, sprite) in &sprites_to_recalculate_aabb { + for (entity, sprite, anchor) in &sprites_to_recalculate_aabb { if let Some(size) = sprite .custom_size .or_else(|| sprite.rect.map(|rect| rect.size())) @@ -209,7 +184,7 @@ pub fn calculate_bounds_2d( }) { let aabb = Aabb { - center: (-sprite.anchor.as_vec() * size).extend(0.0).into(), + center: (-anchor.as_vec() * size).extend(0.0).into(), half_extents: (0.5 * size).extend(0.0).into(), }; commands.entity(entity).try_insert(aabb); @@ -346,12 +321,14 @@ mod test { // Add entities let entity = app .world_mut() - .spawn(Sprite { - rect: Some(Rect::new(0., 0., 0.5, 1.)), - anchor: Anchor::TopRight, - image: image_handle, - ..default() - }) + .spawn(( + Sprite { + rect: Some(Rect::new(0., 0., 0.5, 1.)), + image: image_handle, + ..default() + }, + Anchor::TOP_RIGHT, + )) .id(); // Create AABB diff --git a/crates/bevy_sprite/src/mesh2d/color_material.rs b/crates/bevy_sprite/src/mesh2d/color_material.rs index 83b6930776..d814cfc384 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.rs +++ b/crates/bevy_sprite/src/mesh2d/color_material.rs @@ -1,26 +1,18 @@ use crate::{AlphaMode2d, Material2d, Material2dPlugin}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; +use bevy_asset::{embedded_asset, embedded_path, Asset, AssetApp, AssetPath, Assets, Handle}; use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba}; use bevy_image::Image; use bevy_math::{Affine2, Mat3, Vec4}; use bevy_reflect::prelude::*; use bevy_render::{render_asset::RenderAssets, render_resource::*, texture::GpuImage}; -pub const COLOR_MATERIAL_SHADER_HANDLE: Handle = - weak_handle!("92e0e6e9-ed0b-4db3-89ab-5f65d3678250"); - #[derive(Default)] pub struct ColorMaterialPlugin; impl Plugin for ColorMaterialPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - COLOR_MATERIAL_SHADER_HANDLE, - "color_material.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "color_material.wgsl"); app.add_plugins(Material2dPlugin::::default()) .register_asset_reflect::(); @@ -152,7 +144,9 @@ impl AsBindGroupShaderType for ColorMaterial { impl Material2d for ColorMaterial { fn fragment_shader() -> ShaderRef { - COLOR_MATERIAL_SHADER_HANDLE.into() + ShaderRef::Path( + AssetPath::from_path_buf(embedded_path!("color_material.wgsl")).with_source("embedded"), + ) } fn alpha_mode(&self) -> AlphaMode2d { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 81ad7a9e3e..3f76b516cd 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::prelude::AssetChanged; -use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle}; +use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEventSystems, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, @@ -19,8 +19,9 @@ use bevy_ecs::{ system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_math::FloatOrd; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use bevy_render::camera::extract_cameras; use bevy_render::render_phase::{DrawFunctionId, InputUniformIndex}; use bevy_render::render_resource::CachedRenderPipelineId; use bevy_render::view::RenderVisibleEntities; @@ -42,8 +43,9 @@ use bevy_render::{ renderer::RenderDevice, sync_world::{MainEntity, MainEntityHashMap}, view::{ExtractedView, ViewVisibility}, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; +use bevy_utils::Parallel; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; use tracing::error; @@ -271,7 +273,7 @@ where .add_plugins(RenderAssetPlugin::>::default()) .add_systems( PostUpdate, - check_entities_needing_specialization::.after(AssetEvents), + check_entities_needing_specialization::.after(AssetEventSystems), ); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { @@ -286,7 +288,7 @@ where .add_systems( ExtractSchedule, ( - extract_entities_needs_specialization::, + extract_entities_needs_specialization::.after(extract_cameras), extract_mesh_materials_2d::, ), ) @@ -294,11 +296,11 @@ where Render, ( specialize_material2d_meshes:: - .in_set(RenderSet::PrepareMeshes) + .in_set(RenderSystems::PrepareMeshes) .after(prepare_assets::>) .after(prepare_assets::), queue_material2d_meshes:: - .in_set(RenderSet::QueueMeshes) + .in_set(RenderSystems::QueueMeshes) .after(prepare_assets::>), ), ); @@ -555,10 +557,24 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin pub fn extract_entities_needs_specialization( entities_needing_specialization: Extract>>, mut entity_specialization_ticks: ResMut>, + mut removed_mesh_material_components: Extract>>, + mut specialized_material2d_pipeline_cache: ResMut>, + views: Query<&MainEntity, With>, ticks: SystemChangeTick, ) where M: Material2d, { + // Clean up any despawned entities, we do this first in case the removed material was re-added + // the same frame, thus will appear both in the removed components list and have been added to + // the `EntitiesNeedingSpecialization` collection by triggering the `Changed` filter + for entity in removed_mesh_material_components.read() { + entity_specialization_ticks.remove(&MainEntity::from(entity)); + for view in views { + if let Some(cache) = specialized_material2d_pipeline_cache.get_mut(view) { + cache.remove(&MainEntity::from(entity)); + } + } + } for entity in entities_needing_specialization.iter() { // Update the entity's specialization tick with this run's tick entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); @@ -637,21 +653,28 @@ impl Default for SpecializedMaterial2dViewPipelineCache { pub fn check_entities_needing_specialization( needs_specialization: Query< Entity, - Or<( - Changed, - AssetChanged, - Changed>, - AssetChanged>, - )>, + ( + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + With>, + ), >, + mut par_local: Local>>, mut entities_needing_specialization: ResMut>, ) where M: Material2d, { entities_needing_specialization.clear(); - for entity in &needs_specialization { - entities_needing_specialization.push(entity); - } + + needs_specialization + .par_iter() + .for_each(|entity| par_local.borrow_local_mut().push(entity)); + + par_local.drain_into(&mut entities_needing_specialization); } pub fn specialize_material2d_meshes( @@ -698,6 +721,12 @@ pub fn specialize_material2d_meshes( .or_default(); for (_, visible_entity) in visible_entities.iter::() { + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { + continue; + }; let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); let last_specialized_tick = view_specialized_material_pipeline_cache .get(visible_entity) @@ -709,13 +738,6 @@ pub fn specialize_material2d_meshes( if !needs_specialization { continue; } - - let Some(material_asset_id) = render_material_instances.get(visible_entity) else { - continue; - }; - let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { - continue; - }; let Some(material_2d) = render_materials.get(*material_asset_id) else { continue; }; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 5822d47ed6..a3d9ee3eb2 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,5 +1,6 @@ use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetId, Handle}; +use bevy_render::load_shader_library; use crate::{tonemapping_pipeline_key, Material2dBindGroupId}; use bevy_core_pipeline::tonemapping::DebandDither; @@ -21,7 +22,7 @@ use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo}; use bevy_math::{Affine3, Vec4}; use bevy_render::mesh::MeshTag; use bevy_render::prelude::Msaa; -use bevy_render::RenderSet::PrepareAssets; +use bevy_render::RenderSystems::PrepareAssets; use bevy_render::{ batching::{ gpu_preprocessing::IndirectParametersCpuMetadata, @@ -48,7 +49,7 @@ use bevy_render::{ view::{ ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility, }, - Extract, ExtractSchedule, Render, RenderApp, RenderSet, + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; use bevy_transform::components::GlobalTransform; use nonmax::NonMaxU32; @@ -57,54 +58,15 @@ use tracing::error; #[derive(Default)] pub struct Mesh2dRenderPlugin; -pub const MESH2D_VERTEX_OUTPUT: Handle = - weak_handle!("71e279c7-85a0-46ac-9a76-1586cbf506d0"); -pub const MESH2D_VIEW_TYPES_HANDLE: Handle = - weak_handle!("01087b0d-91e9-46ac-8628-dfe19a7d4b83"); -pub const MESH2D_VIEW_BINDINGS_HANDLE: Handle = - weak_handle!("fbdd8b80-503d-4688-bcec-db29ab4620b2"); -pub const MESH2D_TYPES_HANDLE: Handle = - weak_handle!("199f2089-6e99-4348-9bb1-d82816640a7f"); -pub const MESH2D_BINDINGS_HANDLE: Handle = - weak_handle!("a7bd44cc-0580-4427-9a00-721cf386b6e4"); -pub const MESH2D_FUNCTIONS_HANDLE: Handle = - weak_handle!("0d08ff71-68c1-4017-83e2-bfc34d285c51"); -pub const MESH2D_SHADER_HANDLE: Handle = - weak_handle!("91a7602b-df95-4ea3-9d97-076abcb69d91"); - impl Plugin for Mesh2dRenderPlugin { fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( - app, - MESH2D_VERTEX_OUTPUT, - "mesh2d_vertex_output.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - MESH2D_VIEW_TYPES_HANDLE, - "mesh2d_view_types.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - MESH2D_VIEW_BINDINGS_HANDLE, - "mesh2d_view_bindings.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - MESH2D_TYPES_HANDLE, - "mesh2d_types.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - MESH2D_FUNCTIONS_HANDLE, - "mesh2d_functions.wgsl", - Shader::from_wgsl - ); - load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); + load_shader_library!(app, "mesh2d_vertex_output.wgsl"); + load_shader_library!(app, "mesh2d_view_types.wgsl"); + load_shader_library!(app, "mesh2d_view_bindings.wgsl"); + load_shader_library!(app, "mesh2d_types.wgsl"); + load_shader_library!(app, "mesh2d_functions.wgsl"); + + embedded_asset!(app, "mesh2d.wgsl"); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -119,20 +81,20 @@ impl Plugin for Mesh2dRenderPlugin { sweep_old_entities::, sweep_old_entities::, ) - .in_set(RenderSet::QueueSweep), + .in_set(RenderSystems::QueueSweep), batch_and_prepare_binned_render_phase:: - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), batch_and_prepare_binned_render_phase:: - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), batch_and_prepare_sorted_render_phase:: - .in_set(RenderSet::PrepareResources), + .in_set(RenderSystems::PrepareResources), write_batched_instance_buffer:: - .in_set(RenderSet::PrepareResourcesFlush), - prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), - prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), + .in_set(RenderSystems::PrepareResourcesFlush), + prepare_mesh2d_bind_group.in_set(RenderSystems::PrepareBindGroups), + prepare_mesh2d_view_bind_groups.in_set(RenderSystems::PrepareBindGroups), no_gpu_preprocessing::clear_batched_cpu_instance_buffers:: - .in_set(RenderSet::Cleanup) - .after(RenderSet::Render), + .in_set(RenderSystems::Cleanup) + .after(RenderSystems::Render), ), ); } @@ -168,13 +130,10 @@ impl Plugin for Mesh2dRenderPlugin { // Load the mesh_bindings shader module here as it depends on runtime information about // whether storage buffers are supported, or the maximum uniform buffer binding size. - load_internal_asset!( - app, - MESH2D_BINDINGS_HANDLE, - "mesh2d_bindings.wgsl", - Shader::from_wgsl_with_defs, - mesh_bindings_shader_defs - ); + load_shader_library!(app, "mesh2d_bindings.wgsl", move |settings| *settings = + ShaderSettings { + shader_defs: mesh_bindings_shader_defs.clone() + }); } } @@ -316,6 +275,7 @@ pub fn extract_mesh2d( pub struct Mesh2dPipeline { pub view_layout: BindGroupLayout, pub mesh_layout: BindGroupLayout, + pub shader: Handle, // This dummy white texture is to be used in place of optional textures pub dummy_white_gpu_image: GpuImage, pub per_object_buffer_batch_size: Option, @@ -397,6 +357,7 @@ impl FromWorld for Mesh2dPipeline { per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( render_device, ), + shader: load_embedded_asset!(world, "mesh2d.wgsl"), } } } @@ -690,13 +651,13 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { Ok(RenderPipelineDescriptor { vertex: VertexState { - shader: MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_buffer_layout], }, fragment: Some(FragmentState { - shader: MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl index 0b99482211..dbd73fb171 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl @@ -43,3 +43,7 @@ fn mesh2d_tangent_local_to_world(world_from_local: mat4x4, vertex_tangent: vertex_tangent.w ); } + +fn get_tag(instance_index: u32) -> u32 { + return mesh[instance_index].tag; +} \ No newline at end of file diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl index d5038c818d..e29264e0bf 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl @@ -13,4 +13,5 @@ struct Mesh2d { local_from_world_transpose_b: f32, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, + tag: u32, }; diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 439dd9a643..8ffb12a582 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -1,18 +1,58 @@ -use crate::{Material2d, Material2dKey, Material2dPlugin, Mesh2d}; -use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; -use bevy_color::{Color, LinearRgba}; -use bevy_ecs::prelude::*; +use crate::{ + DrawMesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, SetMesh2dBindGroup, + SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, +}; +use bevy_app::{App, Plugin, PostUpdate, Startup, Update}; +use bevy_asset::{ + embedded_asset, load_embedded_asset, prelude::AssetChanged, AsAssetId, Asset, AssetApp, + AssetEventSystems, AssetId, Assets, Handle, UntypedAssetId, +}; +use bevy_color::{Color, ColorToComponents}; +use bevy_core_pipeline::core_2d::{ + graph::{Core2d, Node2d}, + Camera2d, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + component::Tick, + prelude::*, + query::QueryItem, + system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, +}; +use bevy_platform::{ + collections::{HashMap, HashSet}, + hash::FixedHasher, +}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - extract_resource::ExtractResource, mesh::MeshVertexBufferLayoutRef, prelude::*, + batching::gpu_preprocessing::GpuPreprocessingMode, + camera::ExtractedCamera, + extract_resource::ExtractResource, + mesh::{ + allocator::{MeshAllocator, SlabId}, + Mesh2d, MeshVertexBufferLayoutRef, RenderMesh, + }, + prelude::*, + render_asset::{ + prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, + }, + render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, + render_phase::{ + AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, + PhaseItemBatchSetKey, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, + }, render_resource::*, + renderer::RenderContext, + sync_world::{MainEntity, MainEntityHashMap}, + view::{ + ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, + }, + Extract, Render, RenderApp, RenderDebugFlags, RenderSystems, }; - -use super::MeshMaterial2d; - -pub const WIREFRAME_2D_SHADER_HANDLE: Handle = - weak_handle!("3d8a3853-2927-4de2-9dc7-3971e7e40970"); +use core::{hash::Hash, ops::Range}; +use tracing::error; /// A [`Plugin`] that draws wireframes for 2D meshes. /// @@ -24,35 +64,99 @@ pub const WIREFRAME_2D_SHADER_HANDLE: Handle = /// /// This is a native only feature. #[derive(Debug, Default)] -pub struct Wireframe2dPlugin; +pub struct Wireframe2dPlugin { + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, +} + +impl Wireframe2dPlugin { + /// Creates a new [`Wireframe2dPlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { debug_flags } + } +} + impl Plugin for Wireframe2dPlugin { - fn build(&self, app: &mut bevy_app::App) { - load_internal_asset!( - app, - WIREFRAME_2D_SHADER_HANDLE, - "wireframe2d.wgsl", - Shader::from_wgsl + fn build(&self, app: &mut App) { + embedded_asset!(app, "wireframe2d.wgsl"); + + app.add_plugins(( + BinnedRenderPhasePlugin::::new(self.debug_flags), + RenderAssetPlugin::::default(), + )) + .init_asset::() + .init_resource::>() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .init_resource::() + .add_systems(Startup, setup_global_wireframe_material) + .add_systems( + Update, + ( + global_color_changed.run_if(resource_changed::), + wireframe_color_changed, + // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global + // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. + (apply_wireframe_material, apply_global_wireframe_material).chain(), + ), + ) + .add_systems( + PostUpdate, + check_wireframe_entities_needing_specialization + .after(AssetEventSystems) + .run_if(resource_exists::), ); - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .add_plugins(Material2dPlugin::::default()) - .register_asset_reflect::() - .add_systems(Startup, setup_global_wireframe_material) - .add_systems( - Update, + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_render_graph_node::>(Core2d, Node2d::Wireframe) + .add_render_graph_edges( + Core2d, ( - global_color_changed.run_if(resource_changed::), - wireframe_color_changed, - // Run `apply_global_wireframe_material` after `apply_wireframe_material` so that the global - // wireframe setting is applied to a mesh on the same frame its wireframe marker component is removed. - (apply_wireframe_material, apply_global_wireframe_material).chain(), + Node2d::EndMainPass, + Node2d::Wireframe, + Node2d::PostProcessing, + ), + ) + .add_systems( + ExtractSchedule, + ( + extract_wireframe_2d_camera, + extract_wireframe_entities_needing_specialization, + extract_wireframe_materials, + ), + ) + .add_systems( + Render, + ( + specialize_wireframes + .in_set(RenderSystems::PrepareMeshes) + .after(prepare_assets::) + .after(prepare_assets::), + queue_wireframes + .in_set(RenderSystems::QueueMeshes) + .after(prepare_assets::), ), ); } + + fn finish(&self, app: &mut App) { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app.init_resource::(); + } } /// Enables wireframe rendering for any entity it is attached to. @@ -60,9 +164,248 @@ impl Plugin for Wireframe2dPlugin { /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe2d; +pub struct Wireframe2dPhaseItem { + /// Determines which objects can be placed into a *batch set*. + /// + /// Objects in a single batch set can potentially be multi-drawn together, + /// if it's enabled and the current platform supports it. + pub batch_set_key: Wireframe2dBatchSetKey, + /// The key, which determines which can be batched. + pub bin_key: Wireframe2dBinKey, + /// An entity from which data will be fetched, including the mesh if + /// applicable. + pub representative_entity: (Entity, MainEntity), + /// The ranges of instances. + pub batch_range: Range, + /// An extra index, which is either a dynamic offset or an index in the + /// indirect parameters list. + pub extra_index: PhaseItemExtraIndex, +} + +impl PhaseItem for Wireframe2dPhaseItem { + fn entity(&self) -> Entity { + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 + } + + fn draw_function(&self) -> DrawFunctionId { + self.batch_set_key.draw_function + } + + fn batch_range(&self) -> &Range { + &self.batch_range + } + + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + fn extra_index(&self) -> PhaseItemExtraIndex { + self.extra_index.clone() + } + + fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range, &mut PhaseItemExtraIndex) { + (&mut self.batch_range, &mut self.extra_index) + } +} + +impl CachedRenderPipelinePhaseItem for Wireframe2dPhaseItem { + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.batch_set_key.pipeline + } +} + +impl BinnedPhaseItem for Wireframe2dPhaseItem { + type BinKey = Wireframe2dBinKey; + type BatchSetKey = Wireframe2dBatchSetKey; + + fn new( + batch_set_key: Self::BatchSetKey, + bin_key: Self::BinKey, + representative_entity: (Entity, MainEntity), + batch_range: Range, + extra_index: PhaseItemExtraIndex, + ) -> Self { + Self { + batch_set_key, + bin_key, + representative_entity, + batch_range, + extra_index, + } + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe2dBatchSetKey { + /// The identifier of the render pipeline. + pub pipeline: CachedRenderPipelineId, + + /// The wireframe material asset ID. + pub asset_id: UntypedAssetId, + + /// The function used to draw. + pub draw_function: DrawFunctionId, + /// The ID of the slab of GPU memory that contains vertex data. + /// + /// For non-mesh items, you can fill this with 0 if your items can be + /// multi-drawn, or with a unique value if they can't. + pub vertex_slab: SlabId, + + /// The ID of the slab of GPU memory that contains index data, if present. + /// + /// For non-mesh items, you can safely fill this with `None`. + pub index_slab: Option, +} + +impl PhaseItemBatchSetKey for Wireframe2dBatchSetKey { + fn indexed(&self) -> bool { + self.index_slab.is_some() + } +} + +/// Data that must be identical in order to *batch* phase items together. +/// +/// Note that a *batch set* (if multi-draw is in use) contains multiple batches. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Wireframe2dBinKey { + /// The wireframe mesh asset ID. + pub asset_id: UntypedAssetId, +} + +pub struct SetWireframe2dPushConstants; + +impl RenderCommand

for SetWireframe2dPushConstants { + type Param = ( + SRes, + SRes>, + ); + type ViewQuery = (); + type ItemQuery = (); + + #[inline] + fn render<'w>( + item: &P, + _view: (), + _item_query: Option<()>, + (wireframe_instances, wireframe_assets): SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(wireframe_material) = wireframe_instances.get(&item.main_entity()) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + let Some(wireframe_material) = wireframe_assets.get(*wireframe_material) else { + return RenderCommandResult::Failure("No wireframe material found for entity"); + }; + + pass.set_push_constants( + ShaderStages::FRAGMENT, + 0, + bytemuck::bytes_of(&wireframe_material.color), + ); + RenderCommandResult::Success + } +} + +pub type DrawWireframe2d = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetMesh2dBindGroup<1>, + SetWireframe2dPushConstants, + DrawMesh2d, +); + +#[derive(Resource, Clone)] +pub struct Wireframe2dPipeline { + mesh_pipeline: Mesh2dPipeline, + shader: Handle, +} + +impl FromWorld for Wireframe2dPipeline { + fn from_world(render_world: &mut World) -> Self { + Wireframe2dPipeline { + mesh_pipeline: render_world.resource::().clone(), + shader: load_embedded_asset!(render_world, "wireframe2d.wgsl"), + } + } +} + +impl SpecializedMeshPipeline for Wireframe2dPipeline { + type Key = Mesh2dPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayoutRef, + ) -> Result { + let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + descriptor.label = Some("wireframe_2d_pipeline".into()); + descriptor.push_constant_ranges.push(PushConstantRange { + stages: ShaderStages::FRAGMENT, + range: 0..16, + }); + let fragment = descriptor.fragment.as_mut().unwrap(); + fragment.shader = self.shader.clone(); + descriptor.primitive.polygon_mode = PolygonMode::Line; + descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; + Ok(descriptor) + } +} + +#[derive(Default)] +struct Wireframe2dNode; +impl ViewNode for Wireframe2dNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewTarget, + &'static ViewDepthTexture, + ); + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, view, target, depth): QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let Some(wireframe_phase) = + world.get_resource::>() + else { + return Ok(()); + }; + + let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { + return Ok(()); + }; + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_2d_pass"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { + error!("Error encountered while rendering the stencil phase {err:?}"); + return Err(NodeRunError::DrawError(err)); + } + + Ok(()) + } +} + /// Sets the color of the [`Wireframe2d`] of the entity it is attached to. /// /// If this component is present but there's no [`Wireframe2d`] component, @@ -70,23 +413,28 @@ pub struct Wireframe2d; /// /// This overrides the [`Wireframe2dConfig::default_color`]. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default, Debug, Clone)] +#[reflect(Component, Default, Debug)] pub struct Wireframe2dColor { pub color: Color, } +#[derive(Component, Debug, Clone, Default)] +pub struct ExtractedWireframeColor { + pub color: [f32; 4], +} + /// Disables wireframe rendering for any entity it is attached to. /// It will ignore the [`Wireframe2dConfig`] global setting. /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default, Debug, PartialEq, Clone)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe2d; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource, Debug, Default, Clone)] +#[reflect(Resource, Debug, Default)] pub struct Wireframe2dConfig { - /// Whether to show wireframes for all 2D meshes. + /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component. pub global: bool, /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe2d`] component attached to it will have @@ -95,21 +443,121 @@ pub struct Wireframe2dConfig { pub default_color: Color, } +#[derive(Asset, Reflect, Clone, Debug, Default)] +#[reflect(Clone, Default)] +pub struct Wireframe2dMaterial { + pub color: Color, +} + +pub struct RenderWireframeMaterial { + pub color: [f32; 4], +} + +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)] +#[reflect(Component, Default, Clone, PartialEq)] +pub struct Mesh2dWireframe(pub Handle); + +impl AsAssetId for Mesh2dWireframe { + type Asset = Wireframe2dMaterial; + + fn as_asset_id(&self) -> AssetId { + self.0.id() + } +} + +impl RenderAsset for RenderWireframeMaterial { + type SourceAsset = Wireframe2dMaterial; + type Param = (); + + fn prepare_asset( + source_asset: Self::SourceAsset, + _asset_id: AssetId, + _param: &mut SystemParamItem, + ) -> Result> { + Ok(RenderWireframeMaterial { + color: source_asset.color.to_linear().to_f32_array(), + }) + } +} + +#[derive(Resource, Deref, DerefMut, Default)] +pub struct RenderWireframeInstances(MainEntityHashMap>); + +#[derive(Clone, Resource, Deref, DerefMut, Debug, Default)] +pub struct WireframeEntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, +} + +#[derive(Resource, Deref, DerefMut, Clone, Debug, Default)] +pub struct WireframeEntitySpecializationTicks { + pub entities: MainEntityHashMap, +} + +/// Stores the [`SpecializedWireframeViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut, Default)] +pub struct SpecializedWireframePipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap, +} + +/// Stores the cached render pipeline ID for each entity in a single view, as +/// well as the last time it was changed. +#[derive(Deref, DerefMut, Default)] +pub struct SpecializedWireframeViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, +} + #[derive(Resource)] -struct GlobalWireframe2dMaterial { +struct GlobalWireframeMaterial { // This handle will be reused when the global config is enabled handle: Handle, } +pub fn extract_wireframe_materials( + mut material_instances: ResMut, + changed_meshes_query: Extract< + Query< + (Entity, &ViewVisibility, &Mesh2dWireframe), + Or<(Changed, Changed)>, + >, + >, + mut removed_visibilities_query: Extract>, + mut removed_materials_query: Extract>, +) { + for (entity, view_visibility, material) in &changed_meshes_query { + if view_visibility.get() { + material_instances.insert(entity.into(), material.id()); + } else { + material_instances.remove(&MainEntity::from(entity)); + } + } + + for entity in removed_visibilities_query + .read() + .chain(removed_materials_query.read()) + { + // Only queue a mesh for removal if we didn't pick it up above. + // It's possible that a necessary component was removed and re-added in + // the same frame. + if !changed_meshes_query.contains(entity) { + material_instances.remove(&MainEntity::from(entity)); + } + } +} + fn setup_global_wireframe_material( mut commands: Commands, mut materials: ResMut>, config: Res, ) { // Create the handle used for the global material - commands.insert_resource(GlobalWireframe2dMaterial { + commands.insert_resource(GlobalWireframeMaterial { handle: materials.add(Wireframe2dMaterial { - color: config.default_color.into(), + color: config.default_color, }), }); } @@ -118,10 +566,10 @@ fn setup_global_wireframe_material( fn global_color_changed( config: Res, mut materials: ResMut>, - global_material: Res, + global_material: Res, ) { if let Some(global_material) = materials.get_mut(&global_material.handle) { - global_material.color = config.default_color.into(); + global_material.color = config.default_color; } } @@ -129,13 +577,13 @@ fn global_color_changed( fn wireframe_color_changed( mut materials: ResMut>, mut colors_changed: Query< - (&mut MeshMaterial2d, &Wireframe2dColor), + (&mut Mesh2dWireframe, &Wireframe2dColor), (With, Changed), >, ) { for (mut handle, wireframe_color) in &mut colors_changed { handle.0 = materials.add(Wireframe2dMaterial { - color: wireframe_color.color.into(), + color: wireframe_color.color, }); } } @@ -147,100 +595,277 @@ fn apply_wireframe_material( mut materials: ResMut>, wireframes: Query< (Entity, Option<&Wireframe2dColor>), - ( - With, - Without>, - ), - >, - no_wireframes: Query< - Entity, - ( - With, - With>, - ), + (With, Without), >, + no_wireframes: Query, With)>, mut removed_wireframes: RemovedComponents, - global_material: Res, + global_material: Res, ) { for e in removed_wireframes.read().chain(no_wireframes.iter()) { if let Ok(mut commands) = commands.get_entity(e) { - commands.remove::>(); + commands.remove::(); } } - let mut wireframes_to_spawn = vec![]; - for (e, wireframe_color) in &wireframes { - let material = if let Some(wireframe_color) = wireframe_color { - materials.add(Wireframe2dMaterial { - color: wireframe_color.color.into(), - }) - } else { - // If there's no color specified we can use the global material since it's already set to use the default_color - global_material.handle.clone() - }; - wireframes_to_spawn.push((e, MeshMaterial2d(material))); + let mut material_to_spawn = vec![]; + for (e, maybe_color) in &wireframes { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); + material_to_spawn.push((e, Mesh2dWireframe(material))); } - commands.try_insert_batch(wireframes_to_spawn); + commands.try_insert_batch(material_to_spawn); } -type Wireframe2dFilter = (With, Without, Without); +type WireframeFilter = (With, Without, Without); /// Applies or removes a wireframe material on any mesh without a [`Wireframe2d`] or [`NoWireframe2d`] component. fn apply_global_wireframe_material( mut commands: Commands, config: Res, meshes_without_material: Query< - Entity, - ( - Wireframe2dFilter, - Without>, - ), + (Entity, Option<&Wireframe2dColor>), + (WireframeFilter, Without), >, - meshes_with_global_material: Query< - Entity, - (Wireframe2dFilter, With>), - >, - global_material: Res, + meshes_with_global_material: Query)>, + global_material: Res, + mut materials: ResMut>, ) { if config.global { let mut material_to_spawn = vec![]; - for e in &meshes_without_material { + for (e, maybe_color) in &meshes_without_material { + let material = get_wireframe_material(maybe_color, &mut materials, &global_material); // We only add the material handle but not the Wireframe component // This makes it easy to detect which mesh is using the global material and which ones are user specified - material_to_spawn.push((e, MeshMaterial2d(global_material.handle.clone()))); + material_to_spawn.push((e, Mesh2dWireframe(material))); } commands.try_insert_batch(material_to_spawn); } else { for e in &meshes_with_global_material { - commands - .entity(e) - .remove::>(); + commands.entity(e).remove::(); } } } -#[derive(Default, AsBindGroup, Debug, Clone, Asset, Reflect)] -#[reflect(Clone, Default)] -pub struct Wireframe2dMaterial { - #[uniform(0)] - pub color: LinearRgba, -} - -impl Material2d for Wireframe2dMaterial { - fn fragment_shader() -> ShaderRef { - WIREFRAME_2D_SHADER_HANDLE.into() - } - - fn depth_bias(&self) -> f32 { - 1.0 - } - - fn specialize( - descriptor: &mut RenderPipelineDescriptor, - _layout: &MeshVertexBufferLayoutRef, - _key: Material2dKey, - ) -> Result<(), SpecializedMeshPipelineError> { - descriptor.primitive.polygon_mode = PolygonMode::Line; - Ok(()) +/// Gets a handle to a wireframe material with a fallback on the default material +fn get_wireframe_material( + maybe_color: Option<&Wireframe2dColor>, + wireframe_materials: &mut Assets, + global_material: &GlobalWireframeMaterial, +) -> Handle { + if let Some(wireframe_color) = maybe_color { + wireframe_materials.add(Wireframe2dMaterial { + color: wireframe_color.color, + }) + } else { + // If there's no color specified we can use the global material since it's already set to use the default_color + global_material.handle.clone() + } +} + +fn extract_wireframe_2d_camera( + mut wireframe_2d_phases: ResMut>, + cameras: Extract>>, + mut live_entities: Local>, +) { + live_entities.clear(); + for (main_entity, camera) in &cameras { + if !camera.is_active { + continue; + } + let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); + wireframe_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); + live_entities.insert(retained_view_entity); + } + + // Clear out all dead views. + wireframe_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity)); +} + +pub fn extract_wireframe_entities_needing_specialization( + entities_needing_specialization: Extract>, + mut entity_specialization_ticks: ResMut, + views: Query<&ExtractedView>, + mut specialized_wireframe_pipeline_cache: ResMut, + mut removed_meshes_query: Extract>, + ticks: SystemChangeTick, +) { + for entity in entities_needing_specialization.iter() { + // Update the entity's specialization tick with this run's tick + entity_specialization_ticks.insert((*entity).into(), ticks.this_run()); + } + + for entity in removed_meshes_query.read() { + for view in &views { + if let Some(specialized_wireframe_pipeline_cache) = + specialized_wireframe_pipeline_cache.get_mut(&view.retained_view_entity) + { + specialized_wireframe_pipeline_cache.remove(&MainEntity::from(entity)); + } + } + } +} + +pub fn check_wireframe_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed, + AssetChanged, + )>, + >, + mut entities_needing_specialization: ResMut, +) { + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } +} + +pub fn specialize_wireframes( + render_meshes: Res>, + render_mesh_instances: Res, + render_wireframe_instances: Res, + wireframe_phases: Res>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res, + entity_specialization_ticks: Res, + view_specialization_ticks: Res, + mut specialized_material_pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + pipeline_cache: Res, + ticks: SystemChangeTick, +) { + // Record the retained IDs of all views so that we can expire old + // pipeline IDs. + let mut all_views: HashSet = HashSet::default(); + + for (view, visible_entities) in &views { + all_views.insert(view.retained_view_entity); + + if !wireframe_phases.contains_key(&view.retained_view_entity) { + continue; + } + + let Some(view_key) = view_key_cache.get(&view.retained_view_entity.main_entity) else { + continue; + }; + + let view_tick = view_specialization_ticks + .get(&view.retained_view_entity.main_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(view.retained_view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter::() { + if !render_wireframe_instances.contains_key(visible_entity) { + continue; + }; + let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { + continue; + }; + let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap(); + let last_specialized_tick = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(tick, _)| *tick); + let needs_specialization = last_specialized_tick.is_none_or(|tick| { + view_tick.is_newer_than(tick, ticks.this_run()) + || entity_tick.is_newer_than(tick, ticks.this_run()) + }); + if !needs_specialization { + continue; + } + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + + let mut mesh_key = *view_key; + mesh_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()); + + let pipeline_id = + pipelines.specialize(&pipeline_cache, &pipeline, mesh_key, &mesh.layout); + let pipeline_id = match pipeline_id { + Ok(id) => id, + Err(err) => { + error!("{}", err); + continue; + } + }; + + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } + + // Delete specialized pipelines belonging to views that have expired. + specialized_material_pipeline_cache + .retain(|retained_view_entity, _| all_views.contains(retained_view_entity)); +} + +fn queue_wireframes( + custom_draw_functions: Res>, + render_mesh_instances: Res, + mesh_allocator: Res, + specialized_wireframe_pipeline_cache: Res, + render_wireframe_instances: Res, + mut wireframe_2d_phases: ResMut>, + mut views: Query<(&ExtractedView, &RenderVisibleEntities)>, +) { + for (view, visible_entities) in &mut views { + let Some(wireframe_phase) = wireframe_2d_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let draw_wireframe = custom_draw_functions.read().id::(); + + let Some(view_specialized_material_pipeline_cache) = + specialized_wireframe_pipeline_cache.get(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(wireframe_instance) = render_wireframe_instances.get(visible_entity) else { + continue; + }; + let Some((current_change_tick, pipeline_id)) = view_specialized_material_pipeline_cache + .get(visible_entity) + .map(|(current_change_tick, pipeline_id)| (*current_change_tick, *pipeline_id)) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if wireframe_phase.validate_cached_entity(*visible_entity, current_change_tick) { + continue; + } + let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { + continue; + }; + let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); + let bin_key = Wireframe2dBinKey { + asset_id: mesh_instance.mesh_asset_id.untyped(), + }; + let batch_set_key = Wireframe2dBatchSetKey { + pipeline: pipeline_id, + asset_id: wireframe_instance.untyped(), + draw_function: draw_wireframe, + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + wireframe_phase.add( + batch_set_key, + bin_key, + (*render_entity, *visible_entity), + InputUniformIndex::default(), + if mesh_instance.automatic_batching { + BinnedRenderPhaseType::BatchableMesh + } else { + BinnedRenderPhaseType::UnbatchableMesh + }, + current_change_tick, + ); + } } } diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl index fac02d6456..c7bb3aa791 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.wgsl @@ -1,11 +1,12 @@ #import bevy_sprite::mesh2d_vertex_output::VertexOutput -struct WireframeMaterial { - color: vec4, -}; +struct PushConstants { + color: vec4 +} + +var push_constants: PushConstants; -@group(2) @binding(0) var material: WireframeMaterial; @fragment fn fragment(in: VertexOutput) -> @location(0) vec4 { - return material.color; + return push_constants.color; } diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 5877b116e0..57c1acc6bd 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -1,13 +1,16 @@ //! A [`bevy_picking`] backend for sprites. Works for simple sprites and sprite atlases. Works for -//! sprites with arbitrary transforms. Picking is done based on sprite bounds, not visible pixels. -//! This means a partially transparent sprite is pickable even in its transparent areas. +//! sprites with arbitrary transforms. +//! +//! By default, picking for sprites is based on pixel opacity. +//! A sprite is picked only when a pointer is over an opaque pixel. +//! Alternatively, you can configure picking to be based on sprite bounds. //! //! ## Implementation Notes //! //! - The `position` reported in `HitData` in in world space, and the `normal` is a normalized //! vector provided by the target's `GlobalTransform::back()`. -use crate::Sprite; +use crate::{Anchor, Sprite}; use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_color::Alpha; @@ -20,7 +23,10 @@ use bevy_render::prelude::*; use bevy_transform::prelude::*; use bevy_window::PrimaryWindow; -/// A component that marks cameras that should be used in the [`SpritePickingPlugin`]. +/// An optional component that marks cameras that should be used in the [`SpritePickingPlugin`]. +/// +/// Only needed if [`SpritePickingSettings::require_markers`] is set to `true`, and ignored +/// otherwise. #[derive(Debug, Clone, Default, Component, Reflect)] #[reflect(Debug, Default, Component, Clone)] pub struct SpritePickingCamera; @@ -42,9 +48,10 @@ pub enum SpritePickingMode { #[reflect(Resource, Default)] pub struct SpritePickingSettings { /// When set to `true` sprite picking will only consider cameras marked with - /// [`SpritePickingCamera`]. + /// [`SpritePickingCamera`]. Defaults to `false`. + /// Regardless of this setting, only sprites marked with [`Pickable`] will be considered. /// - /// This setting is provided to give you fine-grained control over which cameras and entities + /// This setting is provided to give you fine-grained control over which cameras /// should be used by the sprite picking backend at runtime. pub require_markers: bool, /// Should the backend count transparent pixels as part of the sprite for picking purposes or should it use the bounding box of the sprite alone. @@ -62,6 +69,7 @@ impl Default for SpritePickingSettings { } } +/// Enables the sprite picking backend, allowing you to click on, hover over and drag sprites. #[derive(Clone)] pub struct SpritePickingPlugin; @@ -71,7 +79,7 @@ impl Plugin for SpritePickingPlugin { .register_type::() .register_type::() .register_type::() - .add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend)); + .add_systems(PreUpdate, sprite_picking.in_set(PickingSystems::Backend)); } } @@ -92,6 +100,7 @@ fn sprite_picking( Entity, &Sprite, &GlobalTransform, + &Anchor, &Pickable, &ViewVisibility, )>, @@ -99,9 +108,9 @@ fn sprite_picking( ) { let mut sorted_sprites: Vec<_> = sprite_query .iter() - .filter_map(|(entity, sprite, transform, pickable, vis)| { + .filter_map(|(entity, sprite, transform, anchor, pickable, vis)| { if !transform.affine().is_nan() && vis.get() { - Some((entity, sprite, transform, pickable)) + Some((entity, sprite, transform, anchor, pickable)) } else { None } @@ -109,7 +118,7 @@ fn sprite_picking( .collect(); // radsort is a stable radix sort that performed better than `slice::sort_by_key` - radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _)| { + radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _, _)| { -transform.translation().z }); @@ -151,7 +160,7 @@ fn sprite_picking( let picks: Vec<(Entity, HitData)> = sorted_sprites .iter() .copied() - .filter_map(|(entity, sprite, sprite_transform, pickable)| { + .filter_map(|(entity, sprite, sprite_transform, anchor, pickable)| { if blocked { return None; } @@ -184,6 +193,7 @@ fn sprite_picking( let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point( cursor_pos_sprite, + *anchor, &images, &texture_atlas_layout, ) else { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 50c117040b..7602addc0b 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,7 +1,7 @@ use core::ops::Range; -use crate::{ComputedTextureSlices, ScalingMode, Sprite, SPRITE_SHADER_HANDLE}; -use bevy_asset::{AssetEvent, AssetId, Assets}; +use crate::{Anchor, ComputedTextureSlices, ScalingMode, Sprite}; +use bevy_asset::{load_embedded_asset, AssetEvent, AssetId, Assets, Handle}; use bevy_color::{ColorToComponents, LinearRgba}; use bevy_core_pipeline::{ core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, @@ -18,7 +18,7 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo}; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity}; use bevy_render::{ render_asset::RenderAssets, @@ -47,6 +47,7 @@ use fixedbitset::FixedBitSet; pub struct SpritePipeline { view_layout: BindGroupLayout, material_layout: BindGroupLayout, + shader: Handle, pub dummy_white_gpu_image: GpuImage, } @@ -124,6 +125,7 @@ impl FromWorld for SpritePipeline { view_layout, material_layout, dummy_white_gpu_image, + shader: load_embedded_asset!(world, "sprite.wgsl"), } } } @@ -267,13 +269,13 @@ impl SpecializedRenderPipeline for SpritePipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: SPRITE_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![instance_rate_vertex_buffer_layout], }, fragment: Some(FragmentState { - shader: SPRITE_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -323,24 +325,37 @@ impl SpecializedRenderPipeline for SpritePipeline { } } +pub struct ExtractedSlice { + pub offset: Vec2, + pub rect: Rect, + pub size: Vec2, +} + pub struct ExtractedSprite { + pub main_entity: Entity, + pub render_entity: Entity, pub transform: GlobalTransform, pub color: LinearRgba, - /// Select an area of the texture - pub rect: Option, /// Change the on-screen size of the sprite - pub custom_size: Option, /// Asset ID of the [`Image`] of this sprite /// PERF: storing an `AssetId` instead of `Handle` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped) pub image_handle_id: AssetId, pub flip_x: bool, pub flip_y: bool, - pub anchor: Vec2, - /// For cases where additional [`ExtractedSprites`] are created during extraction, this stores the - /// entity that caused that creation for use in determining visibility. - pub original_entity: Entity, - pub scaling_mode: Option, - pub render_entity: Entity, + pub kind: ExtractedSpriteKind, +} + +pub enum ExtractedSpriteKind { + /// A single sprite with custom sizing and scaling options + Single { + anchor: Vec2, + rect: Option, + scaling_mode: Option, + custom_size: Option, + }, + /// Indexes into the list of [`ExtractedSlice`]s stored in the [`ExtractedSlices`] resource + /// Used for elements composed from multiple sprites such as text or nine-patched borders + Slices { indices: Range }, } #[derive(Resource, Default)] @@ -348,6 +363,11 @@ pub struct ExtractedSprites { pub sprites: Vec, } +#[derive(Resource, Default)] +pub struct ExtractedSlices { + pub slices: Vec, +} + #[derive(Resource, Default)] pub struct SpriteAssetEvents { pub images: Vec>, @@ -366,8 +386,8 @@ pub fn extract_sprite_events( } pub fn extract_sprites( - mut commands: Commands, mut extracted_sprites: ResMut, + mut extracted_slices: ResMut, texture_atlases: Extract>>, sprite_query: Extract< Query<( @@ -376,24 +396,38 @@ pub fn extract_sprites( &ViewVisibility, &Sprite, &GlobalTransform, + &Anchor, Option<&ComputedTextureSlices>, )>, >, ) { extracted_sprites.sprites.clear(); - for (original_entity, entity, view_visibility, sprite, transform, slices) in sprite_query.iter() + extracted_slices.slices.clear(); + for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in + sprite_query.iter() { if !view_visibility.get() { continue; } if let Some(slices) = slices { - extracted_sprites.sprites.extend(slices.extract_sprites( - &mut commands, - transform, - original_entity, - sprite, - )); + let start = extracted_slices.slices.len(); + extracted_slices + .slices + .extend(slices.extract_slices(sprite, anchor.as_vec())); + let end = extracted_slices.slices.len(); + extracted_sprites.sprites.push(ExtractedSprite { + main_entity, + render_entity, + color: sprite.color.into(), + transform: *transform, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + image_handle_id: sprite.image.id(), + kind: ExtractedSpriteKind::Slices { + indices: start..end, + }, + }); } else { let atlas_rect = sprite .texture_atlas @@ -406,25 +440,26 @@ pub fn extract_sprites( (Some(atlas_rect), Some(mut sprite_rect)) => { sprite_rect.min += atlas_rect.min; sprite_rect.max += atlas_rect.min; - Some(sprite_rect) } }; // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive extracted_sprites.sprites.push(ExtractedSprite { - render_entity: entity, + main_entity, + render_entity, color: sprite.color.into(), transform: *transform, - rect, - // Pass the custom size - custom_size: sprite.custom_size, flip_x: sprite.flip_x, flip_y: sprite.flip_y, image_handle_id: sprite.image.id(), - anchor: sprite.anchor.as_vec(), - original_entity, - scaling_mode: sprite.image_mode.scale(), + kind: ExtractedSpriteKind::Single { + anchor: anchor.as_vec(), + rect, + scaling_mode: sprite.image_mode.scale(), + // Pass the custom size + custom_size: sprite.custom_size, + }, }); } } @@ -553,7 +588,7 @@ pub fn queue_sprites( .reserve(extracted_sprites.sprites.len()); for (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() { - let view_index = extracted_sprite.original_entity.index(); + let view_index = extracted_sprite.main_entity.index(); if !view_entities.contains(view_index as usize) { continue; @@ -568,7 +603,7 @@ pub fn queue_sprites( pipeline, entity: ( extracted_sprite.render_entity, - extracted_sprite.original_entity.into(), + extracted_sprite.main_entity.into(), ), sort_key, // `batch_range` is calculated in `prepare_sprite_image_bind_groups` @@ -622,6 +657,7 @@ pub fn prepare_sprite_image_bind_groups( mut image_bind_groups: ResMut, gpu_images: Res>, extracted_sprites: Res, + extracted_slices: Res, mut phases: ResMut>, events: Res, mut batches: ResMut, @@ -701,112 +737,161 @@ pub fn prepare_sprite_image_bind_groups( }, )); } - - // By default, the size of the quad is the size of the texture - let mut quad_size = batch_image_size; - - // Texture size is the size of the image - let mut texture_size = batch_image_size; - - // If a rect is specified, adjust UVs and the size of the quad - let mut uv_offset_scale = if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - quad_size = rect_size; - // Update texture size to the rect size - // It will help scale properly only portion of the image - texture_size = rect_size; - Vec4::new( - rect.min.x / batch_image_size.x, - rect.max.y / batch_image_size.y, - rect_size.x / batch_image_size.x, - -rect_size.y / batch_image_size.y, - ) - } else { - Vec4::new(0.0, 1.0, 1.0, -1.0) - }; - - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; - } - - // Used for translation of the quad if `TextureScale::Fit...` is specified. - let mut quad_translation = Vec2::ZERO; - - // Scales the texture based on the `texture_scale` field. - if let Some(scaling_mode) = extracted_sprite.scaling_mode { - apply_scaling( + match extracted_sprite.kind { + ExtractedSpriteKind::Single { + anchor, + rect, scaling_mode, - texture_size, - &mut quad_size, - &mut quad_translation, - &mut uv_offset_scale, - ); + custom_size, + } => { + // By default, the size of the quad is the size of the texture + let mut quad_size = batch_image_size; + let mut texture_size = batch_image_size; + + // Calculate vertex data for this item + // If a rect is specified, adjust UVs and the size of the quad + let mut uv_offset_scale = if let Some(rect) = rect { + let rect_size = rect.size(); + quad_size = rect_size; + // Update texture size to the rect size + // It will help scale properly only portion of the image + texture_size = rect_size; + Vec4::new( + rect.min.x / batch_image_size.x, + rect.max.y / batch_image_size.y, + rect_size.x / batch_image_size.x, + -rect_size.y / batch_image_size.y, + ) + } else { + Vec4::new(0.0, 1.0, 1.0, -1.0) + }; + + if extracted_sprite.flip_x { + uv_offset_scale.x += uv_offset_scale.z; + uv_offset_scale.z *= -1.0; + } + if extracted_sprite.flip_y { + uv_offset_scale.y += uv_offset_scale.w; + uv_offset_scale.w *= -1.0; + } + + // Override the size if a custom one is specified + quad_size = custom_size.unwrap_or(quad_size); + + // Used for translation of the quad if `TextureScale::Fit...` is specified. + let mut quad_translation = Vec2::ZERO; + + // Scales the texture based on the `texture_scale` field. + if let Some(scaling_mode) = scaling_mode { + apply_scaling( + scaling_mode, + texture_size, + &mut quad_size, + &mut quad_translation, + &mut uv_offset_scale, + ); + } + + let transform = extracted_sprite.transform.affine() + * Affine3A::from_scale_rotation_translation( + quad_size.extend(1.0), + Quat::IDENTITY, + ((quad_size + quad_translation) * (-anchor - Vec2::splat(0.5))) + .extend(0.0), + ); + + // Store the vertex data and add the item to the render phase + sprite_meta + .sprite_instance_buffer + .push(SpriteInstance::from( + &transform, + &extracted_sprite.color, + &uv_offset_scale, + )); + + current_batch.as_mut().unwrap().get_mut().range.end += 1; + index += 1; + } + ExtractedSpriteKind::Slices { ref indices } => { + for i in indices.clone() { + let slice = &extracted_slices.slices[i]; + let rect = slice.rect; + let rect_size = rect.size(); + + // Calculate vertex data for this item + let mut uv_offset_scale: Vec4; + + // If a rect is specified, adjust UVs and the size of the quad + uv_offset_scale = Vec4::new( + rect.min.x / batch_image_size.x, + rect.max.y / batch_image_size.y, + rect_size.x / batch_image_size.x, + -rect_size.y / batch_image_size.y, + ); + + if extracted_sprite.flip_x { + uv_offset_scale.x += uv_offset_scale.z; + uv_offset_scale.z *= -1.0; + } + if extracted_sprite.flip_y { + uv_offset_scale.y += uv_offset_scale.w; + uv_offset_scale.w *= -1.0; + } + + let transform = extracted_sprite.transform.affine() + * Affine3A::from_scale_rotation_translation( + slice.size.extend(1.0), + Quat::IDENTITY, + (slice.size * -Vec2::splat(0.5) + slice.offset).extend(0.0), + ); + + // Store the vertex data and add the item to the render phase + sprite_meta + .sprite_instance_buffer + .push(SpriteInstance::from( + &transform, + &extracted_sprite.color, + &uv_offset_scale, + )); + + current_batch.as_mut().unwrap().get_mut().range.end += 1; + index += 1; + } + } } - - if extracted_sprite.flip_x { - uv_offset_scale.x += uv_offset_scale.z; - uv_offset_scale.z *= -1.0; - } - if extracted_sprite.flip_y { - uv_offset_scale.y += uv_offset_scale.w; - uv_offset_scale.w *= -1.0; - } - - let transform = extracted_sprite.transform.affine() - * Affine3A::from_scale_rotation_translation( - quad_size.extend(1.0), - Quat::IDENTITY, - ((quad_size + quad_translation) - * (-extracted_sprite.anchor - Vec2::splat(0.5))) - .extend(0.0), - ); - - // Store the vertex data and add the item to the render phase - sprite_meta - .sprite_instance_buffer - .push(SpriteInstance::from( - &transform, - &extracted_sprite.color, - &uv_offset_scale, - )); - transparent_phase.items[batch_item_index] .batch_range_mut() .end += 1; - current_batch.as_mut().unwrap().get_mut().range.end += 1; - index += 1; + } + sprite_meta + .sprite_instance_buffer + .write_buffer(&render_device, &render_queue); + + if sprite_meta.sprite_index_buffer.len() != 6 { + sprite_meta.sprite_index_buffer.clear(); + + // NOTE: This code is creating 6 indices pointing to 4 vertices. + // The vertices form the corners of a quad based on their two least significant bits. + // 10 11 + // + // 00 01 + // The sprite shader can then use the two least significant bits as the vertex index. + // The rest of the properties to transform the vertex positions and UVs (which are + // implicit) are baked into the instance transform, and UV offset and scale. + // See bevy_sprite/src/render/sprite.wgsl for the details. + sprite_meta.sprite_index_buffer.push(2); + sprite_meta.sprite_index_buffer.push(0); + sprite_meta.sprite_index_buffer.push(1); + sprite_meta.sprite_index_buffer.push(1); + sprite_meta.sprite_index_buffer.push(3); + sprite_meta.sprite_index_buffer.push(2); + + sprite_meta + .sprite_index_buffer + .write_buffer(&render_device, &render_queue); } } - sprite_meta - .sprite_instance_buffer - .write_buffer(&render_device, &render_queue); - - if sprite_meta.sprite_index_buffer.len() != 6 { - sprite_meta.sprite_index_buffer.clear(); - - // NOTE: This code is creating 6 indices pointing to 4 vertices. - // The vertices form the corners of a quad based on their two least significant bits. - // 10 11 - // - // 00 01 - // The sprite shader can then use the two least significant bits as the vertex index. - // The rest of the properties to transform the vertex positions and UVs (which are - // implicit) are baked into the instance transform, and UV offset and scale. - // See bevy_sprite/src/render/sprite.wgsl for the details. - sprite_meta.sprite_index_buffer.push(2); - sprite_meta.sprite_index_buffer.push(0); - sprite_meta.sprite_index_buffer.push(1); - sprite_meta.sprite_index_buffer.push(1); - sprite_meta.sprite_index_buffer.push(3); - sprite_meta.sprite_index_buffer.push(2); - - sprite_meta - .sprite_index_buffer - .write_buffer(&render_device, &render_queue); - } } - /// [`RenderCommand`] for sprite rendering. pub type DrawSprite = ( SetItemPipeline, diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 17b24f975c..61461ab640 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -1,5 +1,6 @@ use bevy_asset::{Assets, Handle}; use bevy_color::Color; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_image::{Image, TextureAtlas, TextureAtlasLayout}; use bevy_math::{Rect, UVec2, Vec2}; @@ -14,7 +15,7 @@ use crate::TextureSlicer; /// Describes a sprite to be rendered to a 2D camera #[derive(Component, Debug, Default, Clone, Reflect)] -#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass)] +#[require(Transform, Visibility, SyncToRenderWorld, VisibilityClass, Anchor)] #[reflect(Component, Default, Debug, Clone)] #[component(on_add = view::add_visibility_class::)] pub struct Sprite { @@ -37,8 +38,6 @@ pub struct Sprite { /// When used with a [`TextureAtlas`], the rect /// is offset by the atlas's minimal (top-left) corner position. pub rect: Option, - /// [`Anchor`] point of the sprite in the world - pub anchor: Anchor, /// How the sprite's image will be scaled. pub image_mode: SpriteImageMode, } @@ -85,6 +84,7 @@ impl Sprite { pub fn compute_pixel_space_point( &self, point_relative_to_sprite: Vec2, + anchor: Anchor, images: &Assets, texture_atlases: &Assets, ) -> Result { @@ -111,7 +111,7 @@ impl Sprite { }; let sprite_size = self.custom_size.unwrap_or_else(|| texture_rect.size()); - let sprite_center = -self.anchor.as_vec() * sprite_size; + let sprite_center = -anchor.as_vec() * sprite_size; let mut point_relative_to_sprite_center = point_relative_to_sprite - sprite_center; @@ -240,41 +240,37 @@ pub enum ScalingMode { FitEnd, } -/// How a sprite is positioned relative to its [`Transform`]. -/// It defaults to `Anchor::Center`. -#[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)] +/// Normalized (relative to its size) offset of a 2d renderable entity from its [`Transform`]. +#[derive(Component, Debug, Clone, Copy, PartialEq, Deref, DerefMut, Reflect)] #[reflect(Component, Default, Debug, PartialEq, Clone)] #[doc(alias = "pivot")] -pub enum Anchor { - #[default] - Center, - BottomLeft, - BottomCenter, - BottomRight, - CenterLeft, - CenterRight, - TopLeft, - TopCenter, - TopRight, - /// Custom anchor point. Top left is `(-0.5, 0.5)`, center is `(0.0, 0.0)`. The value will - /// be scaled with the sprite size. - Custom(Vec2), -} +pub struct Anchor(pub Vec2); impl Anchor { + pub const BOTTOM_LEFT: Self = Self(Vec2::new(-0.5, -0.5)); + pub const BOTTOM_CENTER: Self = Self(Vec2::new(0.0, -0.5)); + pub const BOTTOM_RIGHT: Self = Self(Vec2::new(0.5, -0.5)); + pub const CENTER_LEFT: Self = Self(Vec2::new(-0.5, 0.0)); + pub const CENTER: Self = Self(Vec2::ZERO); + pub const CENTER_RIGHT: Self = Self(Vec2::new(0.5, 0.0)); + pub const TOP_LEFT: Self = Self(Vec2::new(-0.5, 0.5)); + pub const TOP_CENTER: Self = Self(Vec2::new(0.0, 0.5)); + pub const TOP_RIGHT: Self = Self(Vec2::new(0.5, 0.5)); + pub fn as_vec(&self) -> Vec2 { - match self { - Anchor::Center => Vec2::ZERO, - Anchor::BottomLeft => Vec2::new(-0.5, -0.5), - Anchor::BottomCenter => Vec2::new(0.0, -0.5), - Anchor::BottomRight => Vec2::new(0.5, -0.5), - Anchor::CenterLeft => Vec2::new(-0.5, 0.0), - Anchor::CenterRight => Vec2::new(0.5, 0.0), - Anchor::TopLeft => Vec2::new(-0.5, 0.5), - Anchor::TopCenter => Vec2::new(0.0, 0.5), - Anchor::TopRight => Vec2::new(0.5, 0.5), - Anchor::Custom(point) => *point, - } + self.0 + } +} + +impl Default for Anchor { + fn default() -> Self { + Self::CENTER + } +} + +impl From for Anchor { + fn from(value: Vec2) -> Self { + Self(value) } } @@ -318,8 +314,14 @@ mod tests { ..Default::default() }; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) + }; assert_eq!(compute(Vec2::new(-2.0, -4.5)), Ok(Vec2::new(0.5, 9.5))); assert_eq!(compute(Vec2::new(0.0, 0.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(0.0, 4.5)), Ok(Vec2::new(2.5, 0.5))); @@ -337,7 +339,12 @@ mod tests { let compute = |point| { sprite - .compute_pixel_space_point(point, &image_assets, &texture_atlas_assets) + .compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) // Round to remove floating point errors. .map(|x| (x * 1e5).round() / 1e5) .map_err(|x| (x * 1e5).round() / 1e5) @@ -358,12 +365,13 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BottomLeft, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(0.5, 0.5))); assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5))); @@ -380,12 +388,13 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::TopRight, ..Default::default() }; + let anchor = Anchor::TOP_RIGHT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 0.5))); assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 0.5))); @@ -402,13 +411,14 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BottomLeft, flip_x: true, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 9.5)), Ok(Vec2::new(4.5, 0.5))); assert_eq!(compute(Vec2::new(2.5, 5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(2.5, 9.5)), Ok(Vec2::new(2.5, 0.5))); @@ -425,13 +435,14 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::TopRight, flip_y: true, ..Default::default() }; + let anchor = Anchor::TOP_RIGHT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(-4.5, -0.5)), Ok(Vec2::new(0.5, 9.5))); assert_eq!(compute(Vec2::new(-2.5, -5.0)), Ok(Vec2::new(2.5, 5.0))); assert_eq!(compute(Vec2::new(-2.5, -0.5)), Ok(Vec2::new(2.5, 9.5))); @@ -449,12 +460,13 @@ mod tests { let sprite = Sprite { image, rect: Some(Rect::new(1.5, 3.0, 3.0, 9.5)), - anchor: Anchor::BottomLeft, ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(2.0, 9.0))); // The pixel is outside the rect, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(2.0, 2.5)), Err(Vec2::new(3.5, 7.0))); @@ -473,16 +485,17 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BottomLeft, texture_atlas: Some(TextureAtlas { layout: texture_atlas, index: 0, }), ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(1.5, 3.5))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(5.0, 1.5))); @@ -501,7 +514,6 @@ mod tests { let sprite = Sprite { image, - anchor: Anchor::BottomLeft, texture_atlas: Some(TextureAtlas { layout: texture_atlas, index: 0, @@ -510,9 +522,11 @@ mod tests { rect: Some(Rect::new(1.5, 1.5, 3.0, 3.0)), ..Default::default() }; + let anchor = Anchor::BOTTOM_LEFT; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point(point, anchor, &image_assets, &texture_atlas_assets) + }; assert_eq!(compute(Vec2::new(0.5, 0.5)), Ok(Vec2::new(3.0, 3.5))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. assert_eq!(compute(Vec2::new(4.0, 2.5)), Err(Vec2::new(6.5, 1.5))); @@ -532,8 +546,14 @@ mod tests { ..Default::default() }; - let compute = - |point| sprite.compute_pixel_space_point(point, &image_assets, &texture_atlas_assets); + let compute = |point| { + sprite.compute_pixel_space_point( + point, + Anchor::default(), + &image_assets, + &texture_atlas_assets, + ) + }; assert_eq!(compute(Vec2::new(30.0, 15.0)), Ok(Vec2::new(4.0, 1.0))); assert_eq!(compute(Vec2::new(-10.0, -15.0)), Ok(Vec2::new(2.0, 4.0))); // The pixel is outside the texture atlas, but is still a valid pixel in the image. diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index 78b84d2977..d4972f0384 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -1,13 +1,10 @@ -use crate::{ExtractedSprite, Sprite, SpriteImageMode, TextureAtlasLayout}; - use super::TextureSlice; +use crate::{ExtractedSlice, Sprite, SpriteImageMode, TextureAtlasLayout}; use bevy_asset::{AssetEvent, Assets}; use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_math::{Rect, Vec2}; -use bevy_platform_support::collections::HashSet; -use bevy_render::sync_world::TemporaryRenderEntity; -use bevy_transform::prelude::*; +use bevy_platform::collections::HashSet; /// Component storing texture slices for tiled or sliced sprite entities /// @@ -16,61 +13,34 @@ use bevy_transform::prelude::*; pub struct ComputedTextureSlices(Vec); impl ComputedTextureSlices { - /// Computes [`ExtractedSprite`] iterator from the sprite slices + /// Computes [`ExtractedSlice`] iterator from the sprite slices /// /// # Arguments /// - /// * `transform` - the sprite entity global transform - /// * `original_entity` - the sprite entity /// * `sprite` - The sprite component - /// * `handle` - The sprite texture handle #[must_use] - pub(crate) fn extract_sprites<'a, 'w, 's>( + pub(crate) fn extract_slices<'a>( &'a self, - commands: &'a mut Commands<'w, 's>, - transform: &'a GlobalTransform, - original_entity: Entity, sprite: &'a Sprite, - ) -> impl ExactSizeIterator + 'a + use<'a, 'w, 's> { + anchor: Vec2, + ) -> impl ExactSizeIterator + 'a { let mut flip = Vec2::ONE; - let [mut flip_x, mut flip_y] = [false; 2]; if sprite.flip_x { flip.x *= -1.0; - flip_x = true; } if sprite.flip_y { flip.y *= -1.0; - flip_y = true; } - self.0.iter().map(move |slice| { - let offset = (slice.offset * flip).extend(0.0); - let transform = transform.mul_transform(Transform::from_translation(offset)); - ExtractedSprite { - render_entity: commands.spawn(TemporaryRenderEntity).id(), - original_entity, - color: sprite.color.into(), - transform, - rect: Some(slice.texture_rect), - custom_size: Some(slice.draw_size), - flip_x, - flip_y, - image_handle_id: sprite.image.id(), - anchor: Self::redepend_anchor_from_sprite_to_slice(sprite, slice), - scaling_mode: sprite.image_mode.scale(), - } + let anchor = anchor + * sprite + .custom_size + .unwrap_or(sprite.rect.unwrap_or_default().size()); + self.0.iter().map(move |slice| ExtractedSlice { + offset: slice.offset * flip - anchor, + rect: slice.texture_rect, + size: slice.draw_size, }) } - - fn redepend_anchor_from_sprite_to_slice(sprite: &Sprite, slice: &TextureSlice) -> Vec2 { - let sprite_size = sprite - .custom_size - .unwrap_or(sprite.rect.unwrap_or_default().size()); - if sprite_size == Vec2::ZERO { - sprite.anchor.as_vec() - } else { - sprite.anchor.as_vec() * sprite_size / slice.draw_size - } - } } /// Generates sprite slices for a [`Sprite`] with [`SpriteImageMode::Sliced`] or [`SpriteImageMode::Sliced`]. The slices diff --git a/crates/bevy_state/Cargo.toml b/crates/bevy_state/Cargo.toml index 3b205fa69c..654218fb28 100644 --- a/crates/bevy_state/Cargo.toml +++ b/crates/bevy_state/Cargo.toml @@ -30,20 +30,18 @@ bevy_app = ["dep:bevy_app"] ## supported platforms. std = [ "bevy_ecs/std", - "bevy_utils/std", "bevy_reflect?/std", "bevy_app?/std", - "bevy_platform_support/std", + "bevy_platform/std", ] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. critical-section = [ "bevy_ecs/critical-section", - "bevy_utils/critical-section", "bevy_app?/critical-section", "bevy_reflect?/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", ] [dependencies] @@ -53,7 +51,7 @@ bevy_state_macros = { path = "macros", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } variadics_please = "1.1" # other diff --git a/crates/bevy_state/macros/src/states.rs b/crates/bevy_state/macros/src/states.rs index 52c133f6ee..57c997156b 100644 --- a/crates/bevy_state/macros/src/states.rs +++ b/crates/bevy_state/macros/src/states.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, LitBool, Pat, Path, Result}; use crate::bevy_state_path; @@ -13,14 +13,16 @@ struct StatesAttrs { fn parse_states_attr(ast: &DeriveInput) -> Result { let mut attrs = StatesAttrs { - scoped_entities_enabled: false, + scoped_entities_enabled: true, }; for attr in ast.attrs.iter() { if attr.path().is_ident(STATES) { attr.parse_nested_meta(|nested| { if nested.path.is_ident(SCOPED_ENTITIES) { - attrs.scoped_entities_enabled = true; + if let Ok(value) = nested.value() { + attrs.scoped_entities_enabled = value.parse::()?.value(); + } Ok(()) } else { Err(nested.error("Unsupported attribute")) diff --git a/crates/bevy_state/src/app.rs b/crates/bevy_state/src/app.rs index 46a23c9f9a..05116cbcc5 100644 --- a/crates/bevy_state/src/app.rs +++ b/crates/bevy_state/src/app.rs @@ -6,9 +6,9 @@ use log::warn; use crate::{ state::{ setup_state_transitions_in_world, ComputedStates, FreelyMutableState, NextState, State, - StateTransition, StateTransitionEvent, StateTransitionSteps, States, SubStates, + StateTransition, StateTransitionEvent, StateTransitionSystems, States, SubStates, }, - state_scoped::clear_state_scoped_entities, + state_scoped::{despawn_entities_on_enter_state, despawn_entities_on_exit_state}, }; #[cfg(feature = "bevy_reflect")] @@ -25,7 +25,7 @@ pub trait AppExtStates { /// These schedules are triggered before [`Update`](bevy_app::Update) and at startup. /// /// If you would like to control how other systems run based on the current state, you can - /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition). + /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`SystemCondition`](bevy_ecs::prelude::SystemCondition). /// /// Note that you can also apply state transitions at other points in the schedule /// by triggering the [`StateTransition`](struct@StateTransition) schedule manually. @@ -41,7 +41,7 @@ pub trait AppExtStates { /// These schedules are triggered before [`Update`](bevy_app::Update) and at startup. /// /// If you would like to control how other systems run based on the current state, you can - /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition). + /// emulate this behavior using the [`in_state`](crate::condition::in_state) [`SystemCondition`](bevy_ecs::prelude::SystemCondition). /// /// Note that you can also apply state transitions at other points in the schedule /// by triggering the [`StateTransition`](struct@StateTransition) schedule manually. @@ -59,10 +59,11 @@ pub trait AppExtStates { /// Enable state-scoped entity clearing for state `S`. /// - /// If the [`States`] trait was derived with the `#[states(scoped_entities)]` attribute, it - /// will be called automatically. + /// This is enabled by default. If you don't want this behavior, add the `#[states(scoped_entities = false)]` + /// attribute when deriving the [`States`] trait. /// - /// For more information refer to [`StateScoped`](crate::state_scoped::StateScoped). + /// For more information refer to [`crate::state_scoped`]. + #[doc(hidden)] fn enable_state_scoped_entities(&mut self) -> &mut Self; #[cfg(feature = "bevy_reflect")] @@ -214,6 +215,7 @@ impl AppExtStates for SubApp { self } + #[doc(hidden)] fn enable_state_scoped_entities(&mut self) -> &mut Self { if !self .world() @@ -222,11 +224,20 @@ impl AppExtStates for SubApp { let name = core::any::type_name::(); warn!("State scoped entities are enabled for state `{}`, but the state isn't installed in the app!", name); } - // We work with [`StateTransition`] in set [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`], - // because [`OnExit`] only runs for one specific variant of the state. + + // Note: We work with `StateTransition` in set + // `StateTransitionSystems::ExitSchedules` rather than `OnExit`, because + // `OnExit` only runs for one specific variant of the state. self.add_systems( StateTransition, - clear_state_scoped_entities::.in_set(StateTransitionSteps::ExitSchedules), + despawn_entities_on_exit_state::.in_set(StateTransitionSystems::ExitSchedules), + ) + // Note: We work with `StateTransition` in set + // `StateTransitionSystems::EnterSchedules` rather than `OnEnter`, because + // `OnEnter` only runs for one specific variant of the state. + .add_systems( + StateTransition, + despawn_entities_on_enter_state::.in_set(StateTransitionSystems::EnterSchedules), ) } @@ -276,6 +287,7 @@ impl AppExtStates for App { self } + #[doc(hidden)] fn enable_state_scoped_entities(&mut self) -> &mut Self { self.main_mut().enable_state_scoped_entities::(); self diff --git a/crates/bevy_state/src/condition.rs b/crates/bevy_state/src/condition.rs index faede71be5..4d9acb8cfe 100644 --- a/crates/bevy_state/src/condition.rs +++ b/crates/bevy_state/src/condition.rs @@ -1,7 +1,7 @@ use crate::state::{State, States}; use bevy_ecs::{change_detection::DetectChanges, system::Res}; -/// A [`Condition`](bevy_ecs::prelude::Condition)-satisfying system that returns `true` +/// A [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying system that returns `true` /// if the state machine exists. /// /// # Example @@ -48,7 +48,7 @@ pub fn state_exists(current_state: Option>>) -> bool { current_state.is_some() } -/// Generates a [`Condition`](bevy_ecs::prelude::Condition)-satisfying closure that returns `true` +/// Generates a [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying closure that returns `true` /// if the state machine is currently in `state`. /// /// Will return `false` if the state does not exist or if not in `state`. @@ -107,7 +107,7 @@ pub fn in_state(state: S) -> impl FnMut(Option>>) -> boo } } -/// A [`Condition`](bevy_ecs::prelude::Condition)-satisfying system that returns `true` +/// A [`SystemCondition`](bevy_ecs::prelude::SystemCondition)-satisfying system that returns `true` /// if the state machine changed state. /// /// To do things on transitions to/from specific states, use their respective OnEnter/OnExit @@ -171,7 +171,7 @@ pub fn state_changed(current_state: Option>>) -> bool { #[cfg(test)] mod tests { - use bevy_ecs::schedule::{Condition, IntoScheduleConfigs, Schedule}; + use bevy_ecs::schedule::{IntoScheduleConfigs, Schedule, SystemCondition}; use crate::prelude::*; use bevy_state_macros::States; diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index b2714b50c5..db40adeeb4 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -28,6 +28,9 @@ //! - A [`StateTransitionEvent`](crate::state::StateTransitionEvent) that gets fired when a given state changes. //! - The [`in_state`](crate::condition::in_state) and [`state_changed`](crate::condition::state_changed) run conditions - which are used //! to determine whether a system should run based on the current state. +//! +//! Bevy also provides ("state-scoped entities")[`crate::state_scoped`] functionality for managing the lifetime of entities in the context of game states. +//! This, especially in combination with system scheduling, enables a flexible and expressive way to manage spawning and despawning entities. #![cfg_attr( any(docsrs, docsrs_dep), @@ -56,8 +59,7 @@ pub mod condition; /// Provides definitions for the basic traits required by the state system pub mod state; -/// Provides [`StateScoped`](crate::state_scoped::StateScoped) and -/// [`clear_state_scoped_entities`](crate::state_scoped::clear_state_scoped_entities) for managing lifetime of entities. +/// Provides tools for managing the lifetime of entities based on state transitions. pub mod state_scoped; #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with methods for registering @@ -89,6 +91,6 @@ pub mod prelude { OnExit, OnTransition, State, StateSet, StateTransition, StateTransitionEvent, States, SubStates, TransitionSchedules, }, - state_scoped::StateScoped, + state_scoped::{DespawnOnEnterState, DespawnOnExitState}, }; } diff --git a/crates/bevy_state/src/state/freely_mutable_state.rs b/crates/bevy_state/src/state/freely_mutable_state.rs index aef72e15fa..8751b3ad29 100644 --- a/crates/bevy_state/src/state/freely_mutable_state.rs +++ b/crates/bevy_state/src/state/freely_mutable_state.rs @@ -17,11 +17,11 @@ pub trait FreelyMutableState: States { fn register_state(schedule: &mut Schedule) { schedule.configure_sets(( ApplyStateTransition::::default() - .in_set(StateTransitionSteps::DependentTransitions), - ExitSchedules::::default().in_set(StateTransitionSteps::ExitSchedules), + .in_set(StateTransitionSystems::DependentTransitions), + ExitSchedules::::default().in_set(StateTransitionSystems::ExitSchedules), TransitionSchedules::::default() - .in_set(StateTransitionSteps::TransitionSchedules), - EnterSchedules::::default().in_set(StateTransitionSteps::EnterSchedules), + .in_set(StateTransitionSystems::TransitionSchedules), + EnterSchedules::::default().in_set(StateTransitionSystems::EnterSchedules), )); schedule diff --git a/crates/bevy_state/src/state/state_set.rs b/crates/bevy_state/src/state/state_set.rs index 5199662027..69a6c41b3d 100644 --- a/crates/bevy_state/src/state/state_set.rs +++ b/crates/bevy_state/src/state/state_set.rs @@ -10,7 +10,7 @@ use self::sealed::StateSetSealed; use super::{ computed_states::ComputedStates, internal_apply_state_transition, last_transition, run_enter, run_exit, run_transition, sub_states::SubStates, take_next_state, ApplyStateTransition, - EnterSchedules, ExitSchedules, NextState, State, StateTransitionEvent, StateTransitionSteps, + EnterSchedules, ExitSchedules, NextState, State, StateTransitionEvent, StateTransitionSystems, States, TransitionSchedules, }; @@ -117,14 +117,14 @@ impl StateSet for S { schedule.configure_sets(( ApplyStateTransition::::default() - .in_set(StateTransitionSteps::DependentTransitions) + .in_set(StateTransitionSystems::DependentTransitions) .after(ApplyStateTransition::::default()), ExitSchedules::::default() - .in_set(StateTransitionSteps::ExitSchedules) + .in_set(StateTransitionSystems::ExitSchedules) .before(ExitSchedules::::default()), - TransitionSchedules::::default().in_set(StateTransitionSteps::TransitionSchedules), + TransitionSchedules::::default().in_set(StateTransitionSystems::TransitionSchedules), EnterSchedules::::default() - .in_set(StateTransitionSteps::EnterSchedules) + .in_set(StateTransitionSystems::EnterSchedules) .after(EnterSchedules::::default()), )); @@ -197,14 +197,14 @@ impl StateSet for S { schedule.configure_sets(( ApplyStateTransition::::default() - .in_set(StateTransitionSteps::DependentTransitions) + .in_set(StateTransitionSystems::DependentTransitions) .after(ApplyStateTransition::::default()), ExitSchedules::::default() - .in_set(StateTransitionSteps::ExitSchedules) + .in_set(StateTransitionSystems::ExitSchedules) .before(ExitSchedules::::default()), - TransitionSchedules::::default().in_set(StateTransitionSteps::TransitionSchedules), + TransitionSchedules::::default().in_set(StateTransitionSystems::TransitionSchedules), EnterSchedules::::default() - .in_set(StateTransitionSteps::EnterSchedules) + .in_set(StateTransitionSystems::EnterSchedules) .after(EnterSchedules::::default()), )); @@ -264,15 +264,15 @@ macro_rules! impl_state_set_sealed_tuples { schedule.configure_sets(( ApplyStateTransition::::default() - .in_set(StateTransitionSteps::DependentTransitions) + .in_set(StateTransitionSystems::DependentTransitions) $(.after(ApplyStateTransition::<$param::RawState>::default()))*, ExitSchedules::::default() - .in_set(StateTransitionSteps::ExitSchedules) + .in_set(StateTransitionSystems::ExitSchedules) $(.before(ExitSchedules::<$param::RawState>::default()))*, TransitionSchedules::::default() - .in_set(StateTransitionSteps::TransitionSchedules), + .in_set(StateTransitionSystems::TransitionSchedules), EnterSchedules::::default() - .in_set(StateTransitionSteps::EnterSchedules) + .in_set(StateTransitionSystems::EnterSchedules) $(.after(EnterSchedules::<$param::RawState>::default()))*, )); @@ -318,15 +318,15 @@ macro_rules! impl_state_set_sealed_tuples { schedule.configure_sets(( ApplyStateTransition::::default() - .in_set(StateTransitionSteps::DependentTransitions) + .in_set(StateTransitionSystems::DependentTransitions) $(.after(ApplyStateTransition::<$param::RawState>::default()))*, ExitSchedules::::default() - .in_set(StateTransitionSteps::ExitSchedules) + .in_set(StateTransitionSystems::ExitSchedules) $(.before(ExitSchedules::<$param::RawState>::default()))*, TransitionSchedules::::default() - .in_set(StateTransitionSteps::TransitionSchedules), + .in_set(StateTransitionSystems::TransitionSchedules), EnterSchedules::::default() - .in_set(StateTransitionSteps::EnterSchedules) + .in_set(StateTransitionSystems::EnterSchedules) $(.after(EnterSchedules::<$param::RawState>::default()))*, )); diff --git a/crates/bevy_state/src/state/states.rs b/crates/bevy_state/src/state/states.rs index 163e689f0a..2bbdd615ba 100644 --- a/crates/bevy_state/src/state/states.rs +++ b/crates/bevy_state/src/state/states.rs @@ -65,7 +65,11 @@ pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug /// `ComputedState` dependencies. const DEPENDENCY_DEPTH: usize = 1; - /// Should [`StateScoped`](crate::state_scoped::StateScoped) be enabled for this state? If set to `true`, - /// the `StateScoped` component will be used to remove entities when changing state. + /// Should [state scoping](crate::state_scoped) be enabled for this state? + /// If set to `true`, the + /// [`DespawnOnEnterState`](crate::state_scoped::DespawnOnEnterState) and + /// [`DespawnOnExitState`](crate::state_scoped::DespawnOnEnterState) + /// components are used to remove entities when entering or exiting the + /// state. const SCOPED_ENTITIES_ENABLED: bool = false; } diff --git a/crates/bevy_state/src/state/sub_states.rs b/crates/bevy_state/src/state/sub_states.rs index 745c4baf0b..c6844eed28 100644 --- a/crates/bevy_state/src/state/sub_states.rs +++ b/crates/bevy_state/src/state/sub_states.rs @@ -7,7 +7,7 @@ pub use bevy_state_macros::SubStates; /// but unlike [`ComputedStates`](crate::state::ComputedStates) - while they exist they can be manually modified. /// /// The default approach to creating [`SubStates`] is using the derive macro, and defining a single source state -/// and value to determine it's existence. +/// and value to determine its existence. /// /// ``` /// # use bevy_ecs::prelude::*; diff --git a/crates/bevy_state/src/state/transitions.rs b/crates/bevy_state/src/state/transitions.rs index 4ce1e6b210..dfe711f245 100644 --- a/crates/bevy_state/src/state/transitions.rs +++ b/crates/bevy_state/src/state/transitions.rs @@ -12,13 +12,13 @@ use super::{resources::State, states::States}; /// The label of a [`Schedule`] that **only** runs whenever [`State`] enters the provided state. /// /// This schedule ignores identity transitions. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnEnter(pub S); /// The label of a [`Schedule`] that **only** runs whenever [`State`] exits the provided state. /// /// This schedule ignores identity transitions. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnExit(pub S); /// The label of a [`Schedule`] that **only** runs whenever [`State`] @@ -27,7 +27,7 @@ pub struct OnExit(pub S); /// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`]. /// /// This schedule will run on identity transitions. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct OnTransition { /// The state being exited. pub exited: S, @@ -37,7 +37,7 @@ pub struct OnTransition { /// Runs [state transitions](States). /// -/// By default, it will be triggered after `PreUpdate`, but +/// By default, it will be triggered once before [`PreStartup`] and then each frame after [`PreUpdate`], but /// you can manually trigger it at arbitrary times by creating an exclusive /// system to run the schedule. /// @@ -49,7 +49,10 @@ pub struct OnTransition { /// let _ = world.try_run_schedule(StateTransition); /// } /// ``` -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +/// +/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html +/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct StateTransition; /// Event sent when any state transition of `S` happens. @@ -68,7 +71,7 @@ pub struct StateTransitionEvent { /// /// These system sets are run sequentially, in the order of the enum variants. #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub enum StateTransitionSteps { +pub enum StateTransitionSystems { /// States apply their transitions from [`NextState`](super::NextState) /// and compute functions based on their parent states. DependentTransitions, @@ -80,6 +83,10 @@ pub enum StateTransitionSteps { EnterSchedules, } +/// Deprecated alias for [`StateTransitionSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `StateTransitionSystems`.")] +pub type StateTransitionSteps = StateTransitionSystems; + #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] /// System set that runs exit schedule(s) for state `S`. pub struct ExitSchedules(PhantomData); @@ -188,10 +195,10 @@ pub fn setup_state_transitions_in_world(world: &mut World) { let mut schedule = Schedule::new(StateTransition); schedule.configure_sets( ( - StateTransitionSteps::DependentTransitions, - StateTransitionSteps::ExitSchedules, - StateTransitionSteps::TransitionSchedules, - StateTransitionSteps::EnterSchedules, + StateTransitionSystems::DependentTransitions, + StateTransitionSystems::ExitSchedules, + StateTransitionSystems::TransitionSchedules, + StateTransitionSystems::EnterSchedules, ) .chain(), ); diff --git a/crates/bevy_state/src/state_scoped.rs b/crates/bevy_state/src/state_scoped.rs index b58017d6e3..1abf0975ea 100644 --- a/crates/bevy_state/src/state_scoped.rs +++ b/crates/bevy_state/src/state_scoped.rs @@ -14,8 +14,7 @@ use crate::state::{StateTransitionEvent, States}; /// Entities marked with this component will be removed /// when the world's state of the matching type no longer matches the supplied value. /// -/// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`]. -/// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities). +/// If you need to disable this behavior, add the attribute `#[states(scoped_entities = false)]` when deriving [`States`]. /// /// ``` /// use bevy_state::prelude::*; @@ -23,7 +22,6 @@ use crate::state::{StateTransitionEvent, States}; /// use bevy_ecs::system::ScheduleSystem; /// /// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] -/// #[states(scoped_entities)] /// enum GameState { /// #[default] /// MainMenu, @@ -36,7 +34,7 @@ use crate::state::{StateTransitionEvent, States}; /// /// fn spawn_player(mut commands: Commands) { /// commands.spawn(( -/// StateScoped(GameState::InGame), +/// DespawnOnExitState(GameState::InGame), /// Player /// )); /// } @@ -44,7 +42,6 @@ use crate::state::{StateTransitionEvent, States}; /// # struct AppMock; /// # impl AppMock { /// # fn init_state(&mut self) {} -/// # fn enable_state_scoped_entities(&mut self) {} /// # fn add_systems(&mut self, schedule: S, systems: impl IntoScheduleConfigs) {} /// # } /// # struct Update; @@ -55,9 +52,9 @@ use crate::state::{StateTransitionEvent, States}; /// ``` #[derive(Component, Clone)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))] -pub struct StateScoped(pub S); +pub struct DespawnOnExitState(pub S); -impl Default for StateScoped +impl Default for DespawnOnExitState where S: States + Default, { @@ -66,12 +63,12 @@ where } } -/// Removes entities marked with [`StateScoped`] -/// when their state no longer matches the world state. -pub fn clear_state_scoped_entities( +/// Despawns entities marked with [`DespawnOnExitState`] when their state no +/// longer matches the world state. +pub fn despawn_entities_on_exit_state( mut commands: Commands, mut transitions: EventReader>, - query: Query<(Entity, &StateScoped)>, + query: Query<(Entity, &DespawnOnExitState)>, ) { // We use the latest event, because state machine internals generate at most 1 // transition event (per type) each frame. No event means no change happened @@ -91,3 +88,72 @@ pub fn clear_state_scoped_entities( } } } + +/// Entities marked with this component will be despawned +/// upon entering the given state. +/// +/// To enable this feature remember to configure your application +/// with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities) on your state(s) of choice. +/// +/// ``` +/// use bevy_state::prelude::*; +/// use bevy_ecs::{prelude::*, system::ScheduleSystem}; +/// +/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] +/// enum GameState { +/// #[default] +/// MainMenu, +/// SettingsMenu, +/// InGame, +/// } +/// +/// # #[derive(Component)] +/// # struct Player; +/// +/// fn spawn_player(mut commands: Commands) { +/// commands.spawn(( +/// DespawnOnEnterState(GameState::MainMenu), +/// Player +/// )); +/// } +/// +/// # struct AppMock; +/// # impl AppMock { +/// # fn init_state(&mut self) {} +/// # fn add_systems(&mut self, schedule: S, systems: impl IntoScheduleConfigs) {} +/// # } +/// # struct Update; +/// # let mut app = AppMock; +/// +/// app.init_state::(); +/// app.add_systems(OnEnter(GameState::InGame), spawn_player); +/// ``` +#[derive(Component, Clone)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))] +pub struct DespawnOnEnterState(pub S); + +/// Despawns entities marked with [`DespawnOnEnterState`] when their state +/// matches the world state. +pub fn despawn_entities_on_enter_state( + mut commands: Commands, + mut transitions: EventReader>, + query: Query<(Entity, &DespawnOnEnterState)>, +) { + // We use the latest event, because state machine internals generate at most 1 + // transition event (per type) each frame. No event means no change happened + // and we skip iterating all entities. + let Some(transition) = transitions.read().last() else { + return; + }; + if transition.entered == transition.exited { + return; + } + let Some(entered) = &transition.entered else { + return; + }; + for (entity, binding) in &query { + if binding.0 == *entered { + commands.entity(entity).despawn(); + } + } +} diff --git a/crates/bevy_state/src/state_scoped_events.rs b/crates/bevy_state/src/state_scoped_events.rs index 846db41b3f..5dc242493e 100644 --- a/crates/bevy_state/src/state_scoped_events.rs +++ b/crates/bevy_state/src/state_scoped_events.rs @@ -8,9 +8,9 @@ use bevy_ecs::{ system::Commands, world::World, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; -use crate::state::{FreelyMutableState, OnExit, StateTransitionEvent}; +use crate::state::{OnExit, StateTransitionEvent, States}; fn clear_event_queue(w: &mut World) { if let Some(mut queue) = w.get_resource_mut::>() { @@ -19,11 +19,11 @@ fn clear_event_queue(w: &mut World) { } #[derive(Resource)] -struct StateScopedEvents { +struct StateScopedEvents { cleanup_fns: HashMap>, } -impl StateScopedEvents { +impl StateScopedEvents { fn add_event(&mut self, state: S) { self.cleanup_fns .entry(state) @@ -41,7 +41,7 @@ impl StateScopedEvents { } } -impl Default for StateScopedEvents { +impl Default for StateScopedEvents { fn default() -> Self { Self { cleanup_fns: HashMap::default(), @@ -49,7 +49,7 @@ impl Default for StateScopedEvents { } } -fn cleanup_state_scoped_event( +fn cleanup_state_scoped_event( mut c: Commands, mut transitions: EventReader>, ) { @@ -70,7 +70,7 @@ fn cleanup_state_scoped_event( }); } -fn add_state_scoped_event_impl( +fn add_state_scoped_event_impl( app: &mut SubApp, _p: PhantomData, state: S, @@ -89,22 +89,23 @@ fn add_state_scoped_event_impl( pub trait StateScopedEventsAppExt { /// Adds an [`Event`] that is automatically cleaned up when leaving the specified `state`. /// - /// Note that event cleanup is ordered ambiguously relative to [`StateScoped`](crate::prelude::StateScoped) entity + /// Note that event cleanup is ordered ambiguously relative to [`DespawnOnEnterState`](crate::prelude::DespawnOnEnterState) + /// and [`DespawnOnExitState`](crate::prelude::DespawnOnExitState) entity /// cleanup and the [`OnExit`] schedule for the target state. All of these (state scoped /// entities and events cleanup, and `OnExit`) occur within schedule [`StateTransition`](crate::prelude::StateTransition) - /// and system set `StateTransitionSteps::ExitSchedules`. - fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self; + /// and system set `StateTransitionSystems::ExitSchedules`. + fn add_state_scoped_event(&mut self, state: impl States) -> &mut Self; } impl StateScopedEventsAppExt for App { - fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + fn add_state_scoped_event(&mut self, state: impl States) -> &mut Self { add_state_scoped_event_impl(self.main_mut(), PhantomData::, state); self } } impl StateScopedEventsAppExt for SubApp { - fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + fn add_state_scoped_event(&mut self, state: impl States) -> &mut Self { add_state_scoped_event_impl(self, PhantomData::, state); self } diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index a8b256f35e..07c20b9750 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -26,28 +26,23 @@ async_executor = ["std", "dep:async-executor"] ## Allows access to the `std` crate. Enabling this feature will prevent compilation ## on `no_std` targets, but provides access to certain additional features on ## supported platforms. -std = [ - "futures-lite/std", - "async-task/std", - "bevy_platform_support/std", - "once_cell/std", -] +std = ["futures-lite/std", "async-task/std", "bevy_platform/std"] ## `critical-section` provides the building blocks for synchronization primitives ## on all platforms, including `no_std`. -critical-section = ["bevy_platform_support/critical-section"] +critical-section = ["bevy_platform/critical-section"] ## Enables use of browser APIs. ## Note this is currently only applicable on `wasm32` architectures. web = [ - "bevy_platform_support/web", + "bevy_platform/web", "dep:wasm-bindgen-futures", "dep:pin-project", "dep:futures-channel", ] [dependencies] -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "alloc", ] } @@ -65,9 +60,6 @@ async-channel = { version = "2.3.0", optional = true } async-io = { version = "2.0.0", optional = true } concurrent-queue = { version = "2.0.0", optional = true } atomic-waker = { version = "1", default-features = false } -once_cell = { version = "1.18", default-features = false, features = [ - "critical-section", -] } crossbeam-queue = { version = "0.3", default-features = false, features = [ "alloc", ] } diff --git a/crates/bevy_tasks/examples/busy_behavior.rs b/crates/bevy_tasks/examples/busy_behavior.rs index c560e58b91..8dc56172df 100644 --- a/crates/bevy_tasks/examples/busy_behavior.rs +++ b/crates/bevy_tasks/examples/busy_behavior.rs @@ -4,7 +4,7 @@ #![expect(clippy::print_stdout, reason = "Allowed in examples.")] -use bevy_platform_support::time::Instant; +use bevy_platform::time::Instant; use bevy_tasks::TaskPoolBuilder; use core::time::Duration; diff --git a/crates/bevy_tasks/examples/idle_behavior.rs b/crates/bevy_tasks/examples/idle_behavior.rs index c68e399a89..06276e916d 100644 --- a/crates/bevy_tasks/examples/idle_behavior.rs +++ b/crates/bevy_tasks/examples/idle_behavior.rs @@ -4,7 +4,7 @@ #![expect(clippy::print_stdout, reason = "Allowed in examples.")] -use bevy_platform_support::time::Instant; +use bevy_platform::time::Instant; use bevy_tasks::TaskPoolBuilder; use core::time::Duration; diff --git a/crates/bevy_tasks/src/edge_executor.rs b/crates/bevy_tasks/src/edge_executor.rs index c082189082..70e11c8a43 100644 --- a/crates/bevy_tasks/src/edge_executor.rs +++ b/crates/bevy_tasks/src/edge_executor.rs @@ -22,9 +22,8 @@ use core::{ use async_task::{Runnable, Task}; use atomic_waker::AtomicWaker; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::{Arc, LazyLock}; use futures_lite::FutureExt; -use once_cell::sync::OnceCell; /// An async executor. /// @@ -51,7 +50,7 @@ use once_cell::sync::OnceCell; /// })); /// ``` pub struct Executor<'a, const C: usize = 64> { - state: OnceCell>>, + state: LazyLock>>, _invariant: PhantomData>, } @@ -67,7 +66,7 @@ impl<'a, const C: usize> Executor<'a, C> { /// ``` pub const fn new() -> Self { Self { - state: OnceCell::new(), + state: LazyLock::new(|| Arc::new(State::new())), _invariant: PhantomData, } } @@ -284,7 +283,7 @@ impl<'a, const C: usize> Executor<'a, C> { /// Returns a reference to the inner state. fn state(&self) -> &Arc> { - self.state.get_or_init(|| Arc::new(State::new())) + &self.state } } @@ -526,15 +525,15 @@ mod drop_tests { use core::task::{Poll, Waker}; use std::sync::Mutex; + use bevy_platform::sync::LazyLock; use futures_lite::future; - use once_cell::sync::Lazy; use super::{Executor, Task}; #[test] fn leaked_executor_leaks_everything() { static DROP: AtomicUsize = AtomicUsize::new(0); - static WAKER: Lazy>> = Lazy::new(Default::default); + static WAKER: LazyLock>> = LazyLock::new(Default::default); let ex: Executor = Default::default(); diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 7f067b3f06..0f9488bcd0 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -1,5 +1,5 @@ use alloc::{string::String, vec::Vec}; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use core::{cell::RefCell, future::Future, marker::PhantomData, mem}; use crate::Task; @@ -8,7 +8,7 @@ use crate::Task; use std::thread_local; #[cfg(not(feature = "std"))] -use bevy_platform_support::sync::{Mutex, PoisonError}; +use bevy_platform::sync::{Mutex, PoisonError}; #[cfg(feature = "std")] use crate::executor::LocalExecutor; @@ -201,23 +201,23 @@ impl TaskPool { { cfg_if::cfg_if! { if #[cfg(all(target_arch = "wasm32", feature = "web"))] { - return Task::wrap_future(future); + Task::wrap_future(future) } else if #[cfg(feature = "std")] { - return LOCAL_EXECUTOR.with(|executor| { + LOCAL_EXECUTOR.with(|executor| { let task = executor.spawn(future); // Loop until all tasks are done while executor.try_tick() {} Task::new(task) - }); + }) } else { - return { + { let task = LOCAL_EXECUTOR.spawn(future); // Loop until all tasks are done while LOCAL_EXECUTOR.try_tick() {} Task::new(task) - }; + } } } } diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 819fbd1235..25255a1e5d 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::executor::FallibleTask; -use bevy_platform_support::sync::Arc; +use bevy_platform::sync::Arc; use concurrent_queue::ConcurrentQueue; use futures_lite::FutureExt; diff --git a/crates/bevy_tasks/src/usages.rs b/crates/bevy_tasks/src/usages.rs index 9563007b03..8b08d5941c 100644 --- a/crates/bevy_tasks/src/usages.rs +++ b/crates/bevy_tasks/src/usages.rs @@ -1,5 +1,5 @@ use super::TaskPool; -use bevy_platform_support::sync::OnceLock; +use bevy_platform::sync::OnceLock; use core::ops::Deref; macro_rules! taskpool { diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index bc939f2daf..5134ecda84 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -27,13 +27,13 @@ bevy_sprite = { path = "../bevy_sprite", version = "0.16.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", "serialize", ] } # other -cosmic-text = { version = "0.13", features = ["shape-run-cache"] } +cosmic-text = { version = "0.14", features = ["shape-run-cache"] } thiserror = { version = "2", default-features = false } serde = { version = "1", features = ["derive"] } smallvec = "1.13" diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index e14157cb38..a10dee5923 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -1,7 +1,7 @@ use bevy_asset::{Assets, Handle}; use bevy_image::{prelude::*, ImageSampler}; use bevy_math::{IVec2, UVec2}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 92b71ef535..8d32127c38 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -1,8 +1,8 @@ -use bevy_asset::{Asset, AssetEvent, AssetId, Assets}; +use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_ecs::{event::EventReader, resource::Resource, system::ResMut}; use bevy_image::prelude::*; use bevy_math::{IVec2, UVec2}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_reflect::TypePath; use bevy_render::{ render_asset::RenderAssetUsages, @@ -53,19 +53,11 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing); /// /// Provides the interface for adding and retrieving rasterized glyphs, and manages the [`FontAtlas`]es. /// -/// A `FontAtlasSet` is an [`Asset`]. -/// -/// There is one `FontAtlasSet` for each font: -/// - When a [`Font`] is loaded as an asset and then used in [`TextFont`](crate::TextFont), -/// a `FontAtlasSet` asset is created from a weak handle to the `Font`. -/// - ~When a font is loaded as a system font, and then used in [`TextFont`](crate::TextFont), -/// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~ -/// (*Note that system fonts are not currently supported by the `TextPipeline`.*) +/// There is at most one `FontAtlasSet` for each font, stored in the `FontAtlasSets` resource. +/// `FontAtlasSet`s are added and updated by the [`queue_text`](crate::pipeline::TextPipeline::queue_text) function. /// /// A `FontAtlasSet` contains one or more [`FontAtlas`]es for each font size. -/// -/// It is used by [`TextPipeline::queue_text`](crate::TextPipeline::queue_text). -#[derive(Debug, TypePath, Asset)] +#[derive(Debug, TypePath)] pub struct FontAtlasSet { font_atlases: HashMap>, } diff --git a/crates/bevy_text/src/glyph.rs b/crates/bevy_text/src/glyph.rs index c761bc0033..ebae713af4 100644 --- a/crates/bevy_text/src/glyph.rs +++ b/crates/bevy_text/src/glyph.rs @@ -23,7 +23,7 @@ pub struct PositionedGlyph { pub span_index: usize, /// The index of the glyph's line. pub line_index: usize, - /// The byte index of the glyph in it's line. + /// The byte index of the glyph in its line. pub byte_index: usize, /// The byte length of the glyph. pub byte_length: usize, diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 670f793c31..2bc74a1aa7 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -66,15 +66,15 @@ pub mod prelude { }; } -use bevy_app::{prelude::*, Animation}; +use bevy_app::{prelude::*, AnimationSystems}; #[cfg(feature = "default_font")] use bevy_asset::{load_internal_binary_asset, Handle}; -use bevy_asset::{AssetApp, AssetEvents}; +use bevy_asset::{AssetApp, AssetEventSystems}; use bevy_ecs::prelude::*; use bevy_render::{ - camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp, + camera::CameraUpdateSystems, view::VisibilitySystems, ExtractSchedule, RenderApp, }; -use bevy_sprite::SpriteSystem; +use bevy_sprite::SpriteSystems; /// The raw data for the default font used by `bevy_text` #[cfg(feature = "default_font")] @@ -87,21 +87,13 @@ pub const DEFAULT_FONT_DATA: &[u8] = include_bytes!("FiraMono-subset.ttf"); #[derive(Default)] pub struct TextPlugin; -/// Text is rendered for two different view projections; -/// 2-dimensional text ([`Text2d`]) is rendered in "world space" with a `BottomToTop` Y-axis, -/// while UI is rendered with a `TopToBottom` Y-axis. -/// This matters for text because the glyph positioning is different in either layout. -/// For `TopToBottom`, 0 is the top of the text, while for `BottomToTop` 0 is the bottom. -pub enum YAxisOrientation { - /// Top to bottom Y-axis orientation, for UI - TopToBottom, - /// Bottom to top Y-axis orientation, for 2d world space - BottomToTop, -} - /// System set in [`PostUpdate`] where all 2d text update systems are executed. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub struct Update2dText; +pub struct Text2dUpdateSystems; + +/// Deprecated alias for [`Text2dUpdateSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `Text2dUpdateSystems`.")] +pub type Update2dText = Text2dUpdateSystems; impl Plugin for TextPlugin { fn build(&self, app: &mut App) { @@ -110,6 +102,7 @@ impl Plugin for TextPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -124,26 +117,26 @@ impl Plugin for TextPlugin { .add_systems( PostUpdate, ( - remove_dropped_font_atlas_sets.before(AssetEvents), + remove_dropped_font_atlas_sets.before(AssetEventSystems), detect_text_needs_rerender::, update_text2d_layout // Potential conflict: `Assets` // In practice, they run independently since `bevy_render::camera_update_system` // will only ever observe its own render target, and `update_text2d_layout` // will never modify a pre-existing `Image` asset. - .ambiguous_with(CameraUpdateSystem), + .ambiguous_with(CameraUpdateSystems), calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds), ) .chain() - .in_set(Update2dText) - .after(Animation), + .in_set(Text2dUpdateSystems) + .after(AnimationSystems), ) .add_systems(Last, trim_cosmic_cache); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems( ExtractSchedule, - extract_text2d_sprite.after(SpriteSystem::ExtractSprites), + extract_text2d_sprite.after(SpriteSystems::ExtractSprites), ); } diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index de6ed5b244..50b983f929 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -9,15 +9,15 @@ use bevy_ecs::{ }; use bevy_image::prelude::*; use bevy_log::{once, warn}; -use bevy_math::{UVec2, Vec2}; -use bevy_platform_support::collections::HashMap; +use bevy_math::{Rect, UVec2, Vec2}; +use bevy_platform::collections::HashMap; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap}; use crate::{ error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText, - LineBreak, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout, YAxisOrientation, + LineBreak, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout, }; /// A wrapper resource around a [`cosmic_text::FontSystem`] @@ -53,11 +53,15 @@ impl Default for SwashCache { /// Information about a font collected as part of preparing for text layout. #[derive(Clone)] -struct FontFaceInfo { - stretch: cosmic_text::fontdb::Stretch, - style: cosmic_text::fontdb::Style, - weight: cosmic_text::fontdb::Weight, - family_name: Arc, +pub struct FontFaceInfo { + /// Width class: + pub stretch: cosmic_text::fontdb::Stretch, + /// Allows italic or oblique faces to be selected + pub style: cosmic_text::fontdb::Style, + /// The degree of blackness or stroke thickness + pub weight: cosmic_text::fontdb::Weight, + /// Font family name + pub family_name: Arc, } /// The `TextPipeline` is used to layout and render text blocks (see `Text`/[`Text2d`](crate::Text2d)). @@ -66,7 +70,7 @@ struct FontFaceInfo { #[derive(Default, Resource)] pub struct TextPipeline { /// Identifies a font [`ID`](cosmic_text::fontdb::ID) by its [`Font`] [`Asset`](bevy_asset::Asset). - map_handle_to_font_id: HashMap, (cosmic_text::fontdb::ID, Arc)>, + pub map_handle_to_font_id: HashMap, (cosmic_text::fontdb::ID, Arc)>, /// Buffered vec for collecting spans. /// /// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10). @@ -188,7 +192,7 @@ impl TextPipeline { buffer.set_rich_text( font_system, spans_iter, - Attrs::new(), + &Attrs::new(), Shaping::Advanced, Some(justify.into()), ); @@ -228,12 +232,12 @@ impl TextPipeline { font_atlas_sets: &mut FontAtlasSets, texture_atlases: &mut Assets, textures: &mut Assets, - y_axis_orientation: YAxisOrientation, computed: &mut ComputedTextBlock, font_system: &mut CosmicFontSystem, swash_cache: &mut SwashCache, ) -> Result<(), TextError> { layout_info.glyphs.clear(); + layout_info.section_rects.clear(); layout_info.size = Default::default(); // Clear this here at the focal point of text rendering to ensure the field's lifecycle has strong boundaries. @@ -265,11 +269,38 @@ impl TextPipeline { let box_size = buffer_dimensions(buffer); let result = buffer.layout_runs().try_for_each(|run| { + let mut current_section: Option = None; + let mut start = 0.; + let mut end = 0.; let result = run .glyphs .iter() .map(move |layout_glyph| (layout_glyph, run.line_y, run.line_i)) .try_for_each(|(layout_glyph, line_y, line_i)| { + match current_section { + Some(section) => { + if section != layout_glyph.metadata { + layout_info.section_rects.push(( + computed.entities[section].entity, + Rect::new( + start, + run.line_top, + end, + run.line_top + run.line_height, + ), + )); + start = end.max(layout_glyph.x); + current_section = Some(layout_glyph.metadata); + } + end = layout_glyph.x + layout_glyph.w; + } + None => { + current_section = Some(layout_glyph.metadata); + start = layout_glyph.x; + end = start + layout_glyph.w; + } + } + let mut temp_glyph; let span_index = layout_glyph.metadata; let font_id = glyph_info[span_index].0; @@ -320,10 +351,6 @@ impl TextPipeline { let x = glyph_size.x as f32 / 2.0 + left + physical_glyph.x as f32; let y = line_y.round() + physical_glyph.y as f32 - top + glyph_size.y as f32 / 2.0; - let y = match y_axis_orientation { - YAxisOrientation::TopToBottom => y, - YAxisOrientation::BottomToTop => box_size.y - y, - }; let position = Vec2::new(x, y); @@ -339,6 +366,12 @@ impl TextPipeline { layout_info.glyphs.push(pos_glyph); Ok(()) }); + if let Some(section) = current_section { + layout_info.section_rects.push(( + computed.entities[section].entity, + Rect::new(start, run.line_top, end, run.line_top + run.line_height), + )); + } result }); @@ -418,6 +451,9 @@ impl TextPipeline { pub struct TextLayoutInfo { /// Scaled and positioned glyphs in screenspace pub glyphs: Vec, + /// Rects bounding the text block's text sections. + /// A text section spanning more than one line will have multiple bounding rects. + pub section_rects: Vec<(Entity, Rect)>, /// The glyphs resulting size pub size: Vec2, } @@ -452,7 +488,8 @@ impl TextMeasureInfo { } } -fn load_font_to_fontdb( +/// Add the font to the cosmic text's `FontSystem`'s in-memory font database +pub fn load_font_to_fontdb( text_font: &TextFont, font_system: &mut cosmic_text::FontSystem, map_handle_to_font_id: &mut HashMap, (cosmic_text::fontdb::ID, Arc)>, @@ -495,7 +532,7 @@ fn get_attrs<'a>( face_info: &'a FontFaceInfo, scale_factor: f64, ) -> Attrs<'a> { - let attrs = Attrs::new() + Attrs::new() .metadata(span_index) .family(Family::Name(&face_info.family_name)) .stretch(face_info.stretch) @@ -508,8 +545,7 @@ fn get_attrs<'a>( } .scale(scale_factor as f32), ) - .color(cosmic_text::Color(color.to_linear().as_u32())); - attrs + .color(cosmic_text::Color(color.to_linear().as_u32())) } /// Calculate the size of the text area for the given buffer. diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index be3e992996..e9e78e3ed2 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -407,6 +407,30 @@ impl TextColor { pub const WHITE: Self = TextColor(Color::WHITE); } +/// The background color of the text for this section. +#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)] +#[reflect(Component, Default, Debug, PartialEq, Clone)] +pub struct TextBackgroundColor(pub Color); + +impl Default for TextBackgroundColor { + fn default() -> Self { + Self(Color::BLACK) + } +} + +impl> From for TextBackgroundColor { + fn from(color: T) -> Self { + Self(color.into()) + } +} + +impl TextBackgroundColor { + /// Black background + pub const BLACK: Self = TextBackgroundColor(Color::BLACK); + /// White background + pub const WHITE: Self = TextBackgroundColor(Color::WHITE); +} + /// Determines how lines will be broken when preventing text from running out of bounds. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)] #[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)] @@ -524,7 +548,7 @@ pub fn detect_text_needs_rerender( )); continue; }; - let mut parent: Entity = span_child_of.parent; + let mut parent: Entity = span_child_of.parent(); // Search for the nearest ancestor with ComputedTextBlock. // Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited @@ -555,7 +579,7 @@ pub fn detect_text_needs_rerender( )); break; }; - parent = next_child_of.parent; + parent = next_child_of.parent(); } } } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index fc750c5ec1..5069804df8 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -2,12 +2,12 @@ use crate::pipeline::CosmicFontSystem; use crate::{ ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBounds, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot, - TextSpanAccess, TextWriter, YAxisOrientation, + TextSpanAccess, TextWriter, }; use bevy_asset::Assets; use bevy_color::LinearRgba; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::entity::hash_set::EntityHashSet; +use bevy_ecs::entity::EntityHashSet; use bevy_ecs::{ change_detection::{DetectChanges, Ref}, component::Component, @@ -26,7 +26,9 @@ use bevy_render::{ view::{NoFrustumCulling, ViewVisibility}, Extract, }; -use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, Sprite}; +use bevy_sprite::{ + Anchor, ExtractedSlice, ExtractedSlices, ExtractedSprite, ExtractedSprites, Sprite, +}; use bevy_transform::components::Transform; use bevy_transform::prelude::GlobalTransform; use bevy_window::{PrimaryWindow, Window}; @@ -136,6 +138,7 @@ pub type Text2dWriter<'w, 's> = TextWriter<'w, 's, Text2d>; pub fn extract_text2d_sprite( mut commands: Commands, mut extracted_sprites: ResMut, + mut extracted_slices: ResMut, texture_atlases: Extract>>, windows: Extract>>, text2d_query: Extract< @@ -149,8 +152,11 @@ pub fn extract_text2d_sprite( &GlobalTransform, )>, >, - text_styles: Extract>, + text_colors: Extract>, ) { + let mut start = extracted_slices.slices.len(); + let mut end = start + 1; + // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 let scale_factor = windows .single() @@ -159,7 +165,7 @@ pub fn extract_text2d_sprite( let scaling = GlobalTransform::from_scale(Vec2::splat(scale_factor.recip()).extend(1.)); for ( - original_entity, + main_entity, view_visibility, computed_block, text_layout_info, @@ -176,21 +182,25 @@ pub fn extract_text2d_sprite( text_bounds.width.unwrap_or(text_layout_info.size.x), text_bounds.height.unwrap_or(text_layout_info.size.y), ); - let bottom_left = - -(anchor.as_vec() + 0.5) * size + (size.y - text_layout_info.size.y) * Vec2::Y; + + let top_left = (Anchor::TOP_LEFT.0 - anchor.as_vec()) * size; let transform = - *global_transform * GlobalTransform::from_translation(bottom_left.extend(0.)) * scaling; + *global_transform * GlobalTransform::from_translation(top_left.extend(0.)) * scaling; let mut color = LinearRgba::WHITE; let mut current_span = usize::MAX; - for PositionedGlyph { - position, - atlas_info, - span_index, - .. - } in &text_layout_info.glyphs + + for ( + i, + PositionedGlyph { + position, + atlas_info, + span_index, + .. + }, + ) in text_layout_info.glyphs.iter().enumerate() { if *span_index != current_span { - color = text_styles + color = text_colors .get( computed_block .entities() @@ -198,25 +208,41 @@ pub fn extract_text2d_sprite( .map(|t| t.entity) .unwrap_or(Entity::PLACEHOLDER), ) - .map(|(_, text_color)| LinearRgba::from(text_color.0)) + .map(|text_color| LinearRgba::from(text_color.0)) .unwrap_or_default(); current_span = *span_index; } - let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); - - extracted_sprites.sprites.push(ExtractedSprite { - render_entity: commands.spawn(TemporaryRenderEntity).id(), - transform: transform * GlobalTransform::from_translation(position.extend(0.)), - color, - rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()), - custom_size: None, - image_handle_id: atlas_info.texture.id(), - flip_x: false, - flip_y: false, - anchor: Anchor::Center.as_vec(), - original_entity, - scaling_mode: None, + let rect = texture_atlases + .get(&atlas_info.texture_atlas) + .unwrap() + .textures[atlas_info.location.glyph_index] + .as_rect(); + extracted_slices.slices.push(ExtractedSlice { + offset: Vec2::new(position.x, -position.y), + rect, + size: rect.size(), }); + + if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { + info.span_index != current_span || info.atlas_info.texture != atlas_info.texture + }) { + let render_entity = commands.spawn(TemporaryRenderEntity).id(); + extracted_sprites.sprites.push(ExtractedSprite { + main_entity, + render_entity, + transform, + color, + image_handle_id: atlas_info.texture.id(), + flip_x: false, + flip_y: false, + kind: bevy_sprite::ExtractedSpriteKind::Slices { + indices: start..end, + }, + }); + start = end; + } + + end += 1; } } } @@ -290,7 +316,6 @@ pub fn update_text2d_layout( &mut font_atlas_sets, &mut texture_atlases, &mut textures, - YAxisOrientation::BottomToTop, computed.as_mut(), &mut font_system, &mut swash_cache, diff --git a/crates/bevy_time/Cargo.toml b/crates/bevy_time/Cargo.toml index 253dc4b90e..520782b519 100644 --- a/crates/bevy_time/Cargo.toml +++ b/crates/bevy_time/Cargo.toml @@ -21,11 +21,7 @@ bevy_reflect = [ ] ## Adds serialization support through `serde`. -serialize = [ - "dep:serde", - "bevy_ecs/serialize", - "bevy_platform_support/serialize", -] +serialize = ["dep:serde", "bevy_ecs/serialize", "bevy_platform/serialize"] # Platform Compatibility @@ -37,7 +33,7 @@ std = [ "bevy_reflect?/std", "bevy_ecs/std", "bevy_app/std", - "bevy_platform_support/std", + "bevy_platform/std", "dep:crossbeam-channel", ] @@ -45,7 +41,7 @@ std = [ ## on all platforms, including `no_std`. critical-section = [ "bevy_ecs/critical-section", - "bevy_platform_support/critical-section", + "bevy_platform/critical-section", "bevy_reflect?/critical-section", "bevy_app/critical-section", ] @@ -55,7 +51,7 @@ critical-section = [ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } # other crossbeam-channel = { version = "0.5.0", default-features = false, features = [ diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index d944303439..cb0b30d13e 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -167,7 +167,7 @@ pub fn repeating_after_delay(duration: Duration) -> impl FnMut(Res

for SetGradientViewBindGroup { + type Param = SRes; + type ViewQuery = Read; + type ItemQuery = (); + + fn render<'w>( + _item: &P, + view_uniform: &'w ViewUniformOffset, + _entity: Option<()>, + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(view_bind_group) = ui_meta.into_inner().view_bind_group.as_ref() else { + return RenderCommandResult::Failure("view_bind_group not available"); + }; + pass.set_bind_group(I, view_bind_group, &[view_uniform.offset]); + RenderCommandResult::Success + } +} + +pub struct DrawGradient; +impl RenderCommand

for DrawGradient { + type Param = SRes; + type ViewQuery = (); + type ItemQuery = Read; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + batch: Option<&'w GradientBatch>, + ui_meta: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let Some(batch) = batch else { + return RenderCommandResult::Skip; + }; + let ui_meta = ui_meta.into_inner(); + let Some(vertices) = ui_meta.vertices.buffer() else { + return RenderCommandResult::Failure("missing vertices to draw ui"); + }; + let Some(indices) = ui_meta.indices.buffer() else { + return RenderCommandResult::Failure("missing indices to draw ui"); + }; + + // Store the vertices + pass.set_vertex_buffer(0, vertices.slice(..)); + // Define how to "connect" the vertices + pass.set_index_buffer(indices.slice(..), 0, IndexFormat::Uint32); + // Draw the vertices + pass.draw_indexed(batch.range.clone(), 0, 0..1); + RenderCommandResult::Success + } +} diff --git a/crates/bevy_ui/src/render/gradient.wgsl b/crates/bevy_ui/src/render/gradient.wgsl new file mode 100644 index 0000000000..0223836f2d --- /dev/null +++ b/crates/bevy_ui/src/render/gradient.wgsl @@ -0,0 +1,198 @@ +#import bevy_render::view::View +#import bevy_ui::ui_node::{ + draw_uinode_background, + draw_uinode_border, +} + +const PI: f32 = 3.14159265358979323846; +const TAU: f32 = 2. * PI; + +const TEXTURED = 1u; +const RIGHT_VERTEX = 2u; +const BOTTOM_VERTEX = 4u; +// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs +const RADIAL: u32 = 16u; +const FILL_START: u32 = 32u; +const FILL_END: u32 = 64u; +const CONIC: u32 = 128u; +const BORDER_LEFT: u32 = 256u; +const BORDER_TOP: u32 = 512u; +const BORDER_RIGHT: u32 = 1024u; +const BORDER_BOTTOM: u32 = 2048u; +const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM; + +fn enabled(flags: u32, mask: u32) -> bool { + return (flags & mask) != 0u; +} + +@group(0) @binding(0) var view: View; + +struct GradientVertexOutput { + @location(0) uv: vec2, + @location(1) @interpolate(flat) size: vec2, + @location(2) @interpolate(flat) flags: u32, + @location(3) @interpolate(flat) radius: vec4, + @location(4) @interpolate(flat) border: vec4, + + // Position relative to the center of the rectangle. + @location(5) point: vec2, + @location(6) @interpolate(flat) g_start: vec2, + @location(7) @interpolate(flat) dir: vec2, + @location(8) @interpolate(flat) start_color: vec4, + @location(9) @interpolate(flat) start_len: f32, + @location(10) @interpolate(flat) end_len: f32, + @location(11) @interpolate(flat) end_color: vec4, + @location(12) @interpolate(flat) hint: f32, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2, + @location(2) flags: u32, + + // x: top left, y: top right, z: bottom right, w: bottom left. + @location(3) radius: vec4, + + // x: left, y: top, z: right, w: bottom. + @location(4) border: vec4, + @location(5) size: vec2, + @location(6) point: vec2, + @location(7) @interpolate(flat) g_start: vec2, + @location(8) @interpolate(flat) dir: vec2, + @location(9) @interpolate(flat) start_color: vec4, + @location(10) @interpolate(flat) start_len: f32, + @location(11) @interpolate(flat) end_len: f32, + @location(12) @interpolate(flat) end_color: vec4, + @location(13) @interpolate(flat) hint: f32 +) -> GradientVertexOutput { + var out: GradientVertexOutput; + out.position = view.clip_from_world * vec4(vertex_position, 1.0); + out.uv = vertex_uv; + out.size = size; + out.flags = flags; + out.radius = radius; + out.border = border; + out.point = point; + out.dir = dir; + out.start_color = start_color; + out.start_len = start_len; + out.end_len = end_len; + out.end_color = end_color; + out.g_start = g_start; + out.hint = hint; + + return out; +} + +@fragment +fn fragment(in: GradientVertexOutput) -> @location(0) vec4 { + var g_distance: f32; + if enabled(in.flags, RADIAL) { + g_distance = radial_distance(in.point, in.g_start, in.dir.x); + } else if enabled(in.flags, CONIC) { + g_distance = conic_distance(in.dir.x, in.point, in.g_start); + } else { + g_distance = linear_distance(in.point, in.g_start, in.dir); + } + + let gradient_color = interpolate_gradient( + g_distance, + in.start_color, + in.start_len, + in.end_color, + in.end_len, + in.hint, + in.flags + ); + + if enabled(in.flags, BORDER_ANY) { + return draw_uinode_border(gradient_color, in.point, in.size, in.radius, in.border, in.flags); + } else { + return draw_uinode_background(gradient_color, in.point, in.size, in.radius, in.border); + } +} + +// This function converts two linear rgb colors to srgb space, mixes them, and then converts the result back to linear rgb space. +fn mix_linear_rgb_in_srgb_space(a: vec4, b: vec4, t: f32) -> vec4 { + let a_srgb = pow(a.rgb, vec3(1. / 2.2)); + let b_srgb = pow(b.rgb, vec3(1. / 2.2)); + let mixed_srgb = mix(a_srgb, b_srgb, t); + return vec4(pow(mixed_srgb, vec3(2.2)), mix(a.a, b.a, t)); +} + +// These functions are used to calculate the distance in gradient space from the start of the gradient to the point. +// The distance in gradient space is then used to interpolate between the start and end colors. + +fn linear_distance( + point: vec2, + g_start: vec2, + g_dir: vec2, +) -> f32 { + return dot(point - g_start, g_dir); +} + +fn radial_distance( + point: vec2, + center: vec2, + ratio: f32, +) -> f32 { + let d = point - center; + return length(vec2(d.x, d.y * ratio)); +} + +fn conic_distance( + start: f32, + point: vec2, + center: vec2, +) -> f32 { + let d = point - center; + let angle = atan2(-d.x, d.y) + PI; + return (((angle - start) % TAU) + TAU) % TAU; +} + +fn interpolate_gradient( + distance: f32, + start_color: vec4, + start_distance: f32, + end_color: vec4, + end_distance: f32, + hint: f32, + flags: u32, +) -> vec4 { + if start_distance == end_distance { + if distance <= start_distance && enabled(flags, FILL_START) { + return start_color; + } + if start_distance <= distance && enabled(flags, FILL_END) { + return end_color; + } + return vec4(0.); + } + + var t = (distance - start_distance) / (end_distance - start_distance); + + if t < 0.0 { + if enabled(flags, FILL_START) { + return start_color; + } + return vec4(0.0); + } + + if 1. < t { + if enabled(flags, FILL_END) { + return end_color; + } + return vec4(0.0); + } + + if t < hint { + t = 0.5 * t / hint; + } else { + t = 0.5 * (1 + (t - hint) / (1.0 - hint)); + } + + // Only color interpolation in SRGB space is supported atm. + return mix_linear_rgb_in_srgb_space(start_color, end_color, t); +} diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b5d24f194d..83140d0f3b 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -6,14 +6,15 @@ pub mod ui_texture_slice_pipeline; #[cfg(feature = "bevy_ui_debug")] mod debug_overlay; +mod gradient; -use crate::widget::ImageNode; +use crate::widget::{ImageNode, ViewportNode}; use crate::{ BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip, ComputedNode, ComputedNodeTarget, Outline, ResolvedBorderRadius, TextShadow, UiAntiAlias, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, weak_handle, AssetEvent, AssetId, Assets, Handle}; +use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_color::{Alpha, ColorToComponents, LinearRgba}; use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; @@ -22,12 +23,13 @@ use bevy_ecs::prelude::*; use bevy_ecs::system::SystemParam; use bevy_image::prelude::*; use bevy_math::{FloatOrd, Mat4, Rect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; +use bevy_render::load_shader_library; use bevy_render::render_graph::{NodeRunError, RenderGraphContext}; use bevy_render::render_phase::ViewSortedRenderPhases; use bevy_render::renderer::RenderContext; use bevy_render::sync_world::MainEntity; use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE; -use bevy_render::view::RetainedViewEntity; +use bevy_render::view::{Hdr, RetainedViewEntity}; use bevy_render::{ camera::Camera, render_asset::RenderAssets, @@ -36,7 +38,7 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, view::{ExtractedView, ViewUniforms}, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSystems, }; use bevy_render::{ render_phase::{PhaseItem, PhaseItemExtraIndex}, @@ -48,10 +50,13 @@ use bevy_render::{ use bevy_sprite::{BorderRect, SpriteAssetEvents}; #[cfg(feature = "bevy_ui_debug")] pub use debug_overlay::UiDebugOptions; +use gradient::GradientPlugin; use crate::{Display, Node}; -use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_text::{ComputedTextBlock, PositionedGlyph, TextColor, TextLayoutInfo}; +use bevy_platform::collections::{HashMap, HashSet}; +use bevy_text::{ + ComputedTextBlock, PositionedGlyph, TextBackgroundColor, TextColor, TextLayoutInfo, +}; use bevy_transform::components::GlobalTransform; use box_shadow::BoxShadowPlugin; use bytemuck::{Pod, Zeroable}; @@ -92,26 +97,32 @@ pub mod stack_z_offsets { pub const BOX_SHADOW: f32 = -0.1; pub const TEXTURE_SLICE: f32 = 0.0; pub const NODE: f32 = 0.0; + pub const GRADIENT: f32 = 0.1; pub const MATERIAL: f32 = 0.18267; } -pub const UI_SHADER_HANDLE: Handle = weak_handle!("7d190d05-545b-42f5-bd85-22a0da85b0f6"); - #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub enum RenderUiSystem { +pub enum RenderUiSystems { ExtractCameraViews, ExtractBoxShadows, ExtractBackgrounds, ExtractImages, ExtractTextureSlice, ExtractBorders, + ExtractViewportNodes, + ExtractTextBackgrounds, ExtractTextShadows, ExtractText, ExtractDebug, + ExtractGradient, } +/// Deprecated alias for [`RenderUiSystems`]. +#[deprecated(since = "0.17.0", note = "Renamed to `RenderUiSystems`.")] +pub type RenderUiSystem = RenderUiSystems; + pub fn build_ui_render(app: &mut App) { - load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); + load_shader_library!(app, "ui.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -129,37 +140,40 @@ pub fn build_ui_render(app: &mut App) { .configure_sets( ExtractSchedule, ( - RenderUiSystem::ExtractCameraViews, - RenderUiSystem::ExtractBoxShadows, - RenderUiSystem::ExtractBackgrounds, - RenderUiSystem::ExtractImages, - RenderUiSystem::ExtractTextureSlice, - RenderUiSystem::ExtractBorders, - RenderUiSystem::ExtractTextShadows, - RenderUiSystem::ExtractText, - RenderUiSystem::ExtractDebug, + RenderUiSystems::ExtractCameraViews, + RenderUiSystems::ExtractBoxShadows, + RenderUiSystems::ExtractBackgrounds, + RenderUiSystems::ExtractImages, + RenderUiSystems::ExtractTextureSlice, + RenderUiSystems::ExtractBorders, + RenderUiSystems::ExtractTextBackgrounds, + RenderUiSystems::ExtractTextShadows, + RenderUiSystems::ExtractText, + RenderUiSystems::ExtractDebug, ) .chain(), ) .add_systems( ExtractSchedule, ( - extract_ui_camera_view.in_set(RenderUiSystem::ExtractCameraViews), - extract_uinode_background_colors.in_set(RenderUiSystem::ExtractBackgrounds), - extract_uinode_images.in_set(RenderUiSystem::ExtractImages), - extract_uinode_borders.in_set(RenderUiSystem::ExtractBorders), - extract_text_shadows.in_set(RenderUiSystem::ExtractTextShadows), - extract_text_sections.in_set(RenderUiSystem::ExtractText), + extract_ui_camera_view.in_set(RenderUiSystems::ExtractCameraViews), + extract_uinode_background_colors.in_set(RenderUiSystems::ExtractBackgrounds), + extract_uinode_images.in_set(RenderUiSystems::ExtractImages), + extract_uinode_borders.in_set(RenderUiSystems::ExtractBorders), + extract_viewport_nodes.in_set(RenderUiSystems::ExtractViewportNodes), + extract_text_background_colors.in_set(RenderUiSystems::ExtractTextBackgrounds), + extract_text_shadows.in_set(RenderUiSystems::ExtractTextShadows), + extract_text_sections.in_set(RenderUiSystems::ExtractText), #[cfg(feature = "bevy_ui_debug")] - debug_overlay::extract_debug_overlay.in_set(RenderUiSystem::ExtractDebug), + debug_overlay::extract_debug_overlay.in_set(RenderUiSystems::ExtractDebug), ), ) .add_systems( Render, ( - queue_uinodes.in_set(RenderSet::Queue), - sort_phase_system::.in_set(RenderSet::PhaseSort), - prepare_uinodes.in_set(RenderSet::PrepareBindGroups), + queue_uinodes.in_set(RenderSystems::Queue), + sort_phase_system::.in_set(RenderSystems::PhaseSort), + prepare_uinodes.in_set(RenderSystems::PrepareBindGroups), ), ); @@ -185,6 +199,7 @@ pub fn build_ui_render(app: &mut App) { } app.add_plugins(UiTextureSlicerPlugin); + app.add_plugins(GradientPlugin); app.add_plugins(BoxShadowPlugin); } @@ -213,7 +228,7 @@ pub struct ExtractedUiNode { #[derive(Clone, Copy, Debug, PartialEq)] pub enum NodeType { Rect, - Border, + Border(u32), // shader flags } pub enum ExtractedUiItem { @@ -506,30 +521,63 @@ pub fn extract_uinode_borders( // Don't extract borders with zero width along all edges if computed_node.border() != BorderRect::ZERO { - if let Some(border_color) = maybe_border_color.filter(|bc| !bc.0.is_fully_transparent()) - { - extracted_uinodes.uinodes.push(ExtractedUiNode { - stack_index: computed_node.stack_index, - color: border_color.0.into(), - rect: Rect { - max: computed_node.size(), - ..Default::default() - }, - image, - clip: maybe_clip.map(|clip| clip.clip), - extracted_camera_entity, - item: ExtractedUiItem::Node { - atlas_scaling: None, - transform: global_transform.compute_matrix(), - flip_x: false, - flip_y: false, - border: computed_node.border(), - border_radius: computed_node.border_radius(), - node_type: NodeType::Border, - }, - main_entity: entity.into(), - render_entity: commands.spawn(TemporaryRenderEntity).id(), - }); + if let Some(border_color) = maybe_border_color { + let border_colors = [ + border_color.left.to_linear(), + border_color.top.to_linear(), + border_color.right.to_linear(), + border_color.bottom.to_linear(), + ]; + + const BORDER_FLAGS: [u32; 4] = [ + shader_flags::BORDER_LEFT, + shader_flags::BORDER_TOP, + shader_flags::BORDER_RIGHT, + shader_flags::BORDER_BOTTOM, + ]; + let mut completed_flags = 0; + + for (i, &color) in border_colors.iter().enumerate() { + if color.is_fully_transparent() { + continue; + } + + let mut border_flags = BORDER_FLAGS[i]; + + if completed_flags & border_flags != 0 { + continue; + } + + for j in i + 1..4 { + if color == border_colors[j] { + border_flags |= BORDER_FLAGS[j]; + } + } + completed_flags |= border_flags; + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index: computed_node.stack_index, + color, + rect: Rect { + max: computed_node.size(), + ..Default::default() + }, + image, + clip: maybe_clip.map(|clip| clip.clip), + extracted_camera_entity, + item: ExtractedUiItem::Node { + atlas_scaling: None, + transform: global_transform.compute_matrix(), + flip_x: false, + flip_y: false, + border: computed_node.border(), + border_radius: computed_node.border_radius(), + node_type: NodeType::Border(border_flags), + }, + main_entity: entity.into(), + render_entity: commands.spawn(TemporaryRenderEntity).id(), + }); + } } } @@ -558,7 +606,7 @@ pub fn extract_uinode_borders( flip_y: false, border: BorderRect::all(computed_node.outline_width()), border_radius: computed_node.outline_radius(), - node_type: NodeType::Border, + node_type: NodeType::Border(shader_flags::BORDER_ALL), }, main_entity: entity.into(), }); @@ -613,6 +661,7 @@ pub fn extract_ui_camera_view( Entity, RenderEntity, &Camera, + Has, Option<&UiAntiAlias>, Option<&BoxShadowSamples>, ), @@ -623,7 +672,7 @@ pub fn extract_ui_camera_view( ) { live_entities.clear(); - for (main_entity, render_entity, camera, ui_anti_alias, shadow_samples) in &query { + for (main_entity, render_entity, camera, hdr, ui_anti_alias, shadow_samples) in &query { // ignore inactive cameras if !camera.is_active { commands @@ -659,7 +708,7 @@ pub fn extract_ui_camera_view( UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, ), clip_from_world: None, - hdr: camera.hdr, + hdr, viewport: UVec4::from(( physical_viewport_rect.min, physical_viewport_rect.size(), @@ -692,6 +741,69 @@ pub fn extract_ui_camera_view( transparent_render_phases.retain(|entity, _| live_entities.contains(entity)); } +pub fn extract_viewport_nodes( + mut commands: Commands, + mut extracted_uinodes: ResMut, + camera_query: Extract>, + uinode_query: Extract< + Query<( + Entity, + &ComputedNode, + &GlobalTransform, + &InheritedVisibility, + Option<&CalculatedClip>, + &ComputedNodeTarget, + &ViewportNode, + )>, + >, + camera_map: Extract, +) { + let mut camera_mapper = camera_map.get_mapper(); + for (entity, uinode, transform, inherited_visibility, clip, camera, viewport_node) in + &uinode_query + { + // Skip invisible images + if !inherited_visibility.get() || uinode.is_empty() { + continue; + } + + let Some(extracted_camera_entity) = camera_mapper.map(camera) else { + continue; + }; + + let Some(image) = camera_query + .get(viewport_node.camera) + .ok() + .and_then(|camera| camera.target.as_image()) + else { + continue; + }; + + extracted_uinodes.uinodes.push(ExtractedUiNode { + render_entity: commands.spawn(TemporaryRenderEntity).id(), + stack_index: uinode.stack_index, + color: LinearRgba::WHITE, + rect: Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + clip: clip.map(|clip| clip.clip), + image: image.id(), + extracted_camera_entity, + item: ExtractedUiItem::Node { + atlas_scaling: None, + transform: transform.compute_matrix(), + flip_x: false, + flip_y: false, + border: uinode.border(), + border_radius: uinode.border_radius(), + node_type: NodeType::Rect, + }, + main_entity: entity.into(), + }); + } +} + pub fn extract_text_sections( mut commands: Commands, mut extracted_uinodes: ResMut, @@ -879,6 +991,70 @@ pub fn extract_text_shadows( } } +pub fn extract_text_background_colors( + mut commands: Commands, + mut extracted_uinodes: ResMut, + uinode_query: Extract< + Query<( + Entity, + &ComputedNode, + &GlobalTransform, + &InheritedVisibility, + Option<&CalculatedClip>, + &ComputedNodeTarget, + &TextLayoutInfo, + )>, + >, + text_background_colors_query: Extract>, + camera_map: Extract, +) { + let mut camera_mapper = camera_map.get_mapper(); + for (entity, uinode, global_transform, inherited_visibility, clip, camera, text_layout_info) in + &uinode_query + { + // Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`) + if !inherited_visibility.get() || uinode.is_empty() { + continue; + } + + let Some(extracted_camera_entity) = camera_mapper.map(camera) else { + continue; + }; + + let transform = global_transform.affine() + * bevy_math::Affine3A::from_translation(-0.5 * uinode.size().extend(0.)); + + for &(section_entity, rect) in text_layout_info.section_rects.iter() { + let Ok(text_background_color) = text_background_colors_query.get(section_entity) else { + continue; + }; + + extracted_uinodes.uinodes.push(ExtractedUiNode { + render_entity: commands.spawn(TemporaryRenderEntity).id(), + stack_index: uinode.stack_index, + color: text_background_color.0.to_linear(), + rect: Rect { + min: Vec2::ZERO, + max: rect.size(), + }, + clip: clip.map(|clip| clip.clip), + image: AssetId::default(), + extracted_camera_entity, + item: ExtractedUiItem::Node { + atlas_scaling: None, + transform: transform * Mat4::from_translation(rect.center().extend(0.)), + flip_x: false, + flip_y: false, + border: uinode.border(), + border_radius: uinode.border_radius(), + node_type: NodeType::Rect, + }, + main_entity: entity.into(), + }); + } + } +} + #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] struct UiVertex { @@ -938,7 +1114,15 @@ pub mod shader_flags { pub const TEXTURED: u32 = 1; /// Ordering: top left, top right, bottom right, bottom left. pub const CORNERS: [u32; 4] = [0, 2, 2 | 4, 4]; - pub const BORDER: u32 = 8; + pub const RADIAL: u32 = 16; + pub const FILL_START: u32 = 32; + pub const FILL_END: u32 = 64; + pub const CONIC: u32 = 128; + pub const BORDER_LEFT: u32 = 256; + pub const BORDER_TOP: u32 = 512; + pub const BORDER_RIGHT: u32 = 1024; + pub const BORDER_BOTTOM: u32 = 2048; + pub const BORDER_ALL: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM; } pub fn queue_uinodes( @@ -946,26 +1130,34 @@ pub fn queue_uinodes( ui_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut render_views: Query<(&UiCameraView, Option<&UiAntiAlias>), With>, + render_views: Query<(&UiCameraView, Option<&UiAntiAlias>), With>, camera_views: Query<&ExtractedView>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); + let mut current_camera_entity = Entity::PLACEHOLDER; + let mut current_phase = None; + for (index, extracted_uinode) in extracted_uinodes.uinodes.iter().enumerate() { - let entity = extracted_uinode.render_entity; - let Ok((default_camera_view, ui_anti_alias)) = - render_views.get_mut(extracted_uinode.extracted_camera_entity) - else { - continue; - }; + if current_camera_entity != extracted_uinode.extracted_camera_entity { + current_phase = render_views + .get(extracted_uinode.extracted_camera_entity) + .ok() + .and_then(|(default_camera_view, ui_anti_alias)| { + camera_views + .get(default_camera_view.0) + .ok() + .and_then(|view| { + transparent_render_phases + .get_mut(&view.retained_view_entity) + .map(|transparent_phase| (view, ui_anti_alias, transparent_phase)) + }) + }); + current_camera_entity = extracted_uinode.extracted_camera_entity; + } - let Ok(view) = camera_views.get(default_camera_view.0) else { - continue; - }; - - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + let Some((view, ui_anti_alias, transparent_phase)) = current_phase.as_mut() else { continue; }; @@ -977,10 +1169,11 @@ pub fn queue_uinodes( anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), }, ); + transparent_phase.add(TransparentUi { draw_function, pipeline, - entity: (entity, extracted_uinode.main_entity), + entity: (extracted_uinode.render_entity, extracted_uinode.main_entity), sort_key: FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE), index, // batch_range will be calculated in prepare_uinodes @@ -1238,8 +1431,8 @@ pub fn prepare_uinodes( }; let color = extracted_uinode.color.to_f32_array(); - if *node_type == NodeType::Border { - flags |= shader_flags::BORDER; + if let NodeType::Border(border_flags) = *node_type { + flags |= border_flags; } for i in 0..4 { diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index dd465515c5..c020e038fb 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -1,3 +1,4 @@ +use bevy_asset::{load_embedded_asset, Handle}; use bevy_ecs::prelude::*; use bevy_image::BevyDefault as _; use bevy_render::{ @@ -13,6 +14,7 @@ use bevy_render::{ pub struct UiPipeline { pub view_layout: BindGroupLayout, pub image_layout: BindGroupLayout, + pub shader: Handle, } impl FromWorld for UiPipeline { @@ -41,6 +43,7 @@ impl FromWorld for UiPipeline { UiPipeline { view_layout, image_layout, + shader: load_embedded_asset!(world, "ui.wgsl"), } } } @@ -84,13 +87,13 @@ impl SpecializedRenderPipeline for UiPipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: super::UI_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_layout], }, fragment: Some(FragmentState { - shader: super::UI_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 3fd339405d..e7c7ec4350 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -1,9 +1,16 @@ +#define_import_path bevy_ui::ui_node + #import bevy_render::view::View const TEXTURED = 1u; const RIGHT_VERTEX = 2u; const BOTTOM_VERTEX = 4u; -const BORDER: u32 = 8u; +// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs +const BORDER_LEFT: u32 = 256u; +const BORDER_TOP: u32 = 512u; +const BORDER_RIGHT: u32 = 1024u; +const BORDER_BOTTOM: u32 = 2048u; +const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM; fn enabled(flags: u32, mask: u32) -> bool { return (flags & mask) != 0u; @@ -114,35 +121,62 @@ fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, in return sd_rounded_box(inner_point, inner_size, r); } +fn nearest_border_active(point_vs_mid: vec2, size: vec2, width: vec4, flags: u32) -> bool { + if (flags & BORDER_ANY) == BORDER_ANY { + return true; + } + + // get point vs top left + let point = clamp(point_vs_mid + size * 0.49999, vec2(0.0), size); + + let left = point.x / width.x; + let top = point.y / width.y; + let right = (size.x - point.x) / width.z; + let bottom = (size.y - point.y) / width.w; + + let min_dist = min(min(left, top), min(right, bottom)); + + return (enabled(flags, BORDER_LEFT) && min_dist == left) || + (enabled(flags, BORDER_TOP) && min_dist == top) || + (enabled(flags, BORDER_RIGHT) && min_dist == right) || + (enabled(flags, BORDER_BOTTOM) && min_dist == bottom); +} + // get alpha for antialiasing for sdf fn antialias(distance: f32) -> f32 { // Using the fwidth(distance) was causing artifacts, so just use the distance. return saturate(0.5 - distance); } -fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { - // Only use the color sampled from the texture if the `TEXTURED` flag is enabled. - // This allows us to draw both textured and untextured shapes together in the same batch. - let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED)); - +fn draw_uinode_border( + color: vec4, + point: vec2, + size: vec2, + radius: vec4, + border: vec4, + flags: u32, +) -> vec4 { // Signed distances. The magnitude is the distance of the point from the edge of the shape. // * Negative values indicate that the point is inside the shape. // * Zero values indicate the point is on the edge of the shape. // * Positive values indicate the point is outside the shape. // Signed distance from the exterior boundary. - let external_distance = sd_rounded_box(in.point, in.size, in.radius); + let external_distance = sd_rounded_box(point, size, radius); // Signed distance from the border's internal edge (the signed distance is negative if the point // is inside the rect but not on the border). // If the border size is set to zero, this is the same as the external distance. - let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border); + let internal_distance = sd_inset_rounded_box(point, size, radius, border); // Signed distance from the border (the intersection of the rect with its border). // Points inside the border have negative signed distance. Any point outside the border, whether // outside the outside edge, or inside the inner edge have positive signed distance. let border_distance = max(external_distance, -internal_distance); + // check if this node should apply color for the nearest border + let nearest_border = select(0.0, 1.0, nearest_border_active(point, size, border, flags)); + #ifdef ANTI_ALIAS // At external edges with no border, `border_distance` is equal to zero. // This select statement ensures we only perform anti-aliasing where a non-zero width border @@ -154,14 +188,18 @@ fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { #endif // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. - return vec4(color.rgb, saturate(color.a * t)); + return vec4(color.rgb, saturate(color.a * t * nearest_border)); } -fn draw_background(in: VertexOutput, texture_color: vec4) -> vec4 { - let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED)); - +fn draw_uinode_background( + color: vec4, + point: vec2, + size: vec2, + radius: vec4, + border: vec4, +) -> vec4 { // When drawing the background only draw the internal area and not the border. - let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border); + let internal_distance = sd_inset_rounded_box(point, size, radius, border); #ifdef ANTI_ALIAS let t = antialias(internal_distance); @@ -176,9 +214,13 @@ fn draw_background(in: VertexOutput, texture_color: vec4) -> vec4 { fn fragment(in: VertexOutput) -> @location(0) vec4 { let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv); - if enabled(in.flags, BORDER) { - return draw(in, texture_color); + // Only use the color sampled from the texture if the `TEXTURED` flag is enabled. + // This allows us to draw both textured and untextured shapes together in the same batch. + let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED)); + + if enabled(in.flags, BORDER_ANY) { + return draw_uinode_border(color, in.point, in.size, in.radius, in.border, in.flags); } else { - return draw_background(in, texture_color); + return draw_uinode_background(color, in.point, in.size, in.radius, in.border); } } diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index fb893b390e..02fab4fdee 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -12,7 +12,6 @@ use bevy_ecs::{ }; use bevy_image::BevyDefault as _; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; -use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity}; use bevy_render::{ extract_component::ExtractComponentPlugin, globals::{GlobalsBuffer, GlobalsUniform}, @@ -21,18 +20,16 @@ use bevy_render::{ render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, view::*, - Extract, ExtractSchedule, Render, RenderSet, + Extract, ExtractSchedule, Render, RenderSystems, +}; +use bevy_render::{ + load_shader_library, + sync_world::{MainEntity, TemporaryRenderEntity}, }; use bevy_sprite::BorderRect; use bevy_transform::prelude::GlobalTransform; use bytemuck::{Pod, Zeroable}; -pub const UI_MATERIAL_SHADER_HANDLE: Handle = - weak_handle!("b5612b7b-aed5-41b4-a930-1d1588239fcd"); - -const UI_VERTEX_OUTPUT_SHADER_HANDLE: Handle = - weak_handle!("1d97ca3e-eaa8-4bc5-a676-e8e9568c472e"); - /// Adds the necessary ECS resources and render logic to enable rendering entities using the given /// [`UiMaterial`] asset type (which includes [`UiMaterial`] types). pub struct UiMaterialPlugin(PhantomData); @@ -48,18 +45,10 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - UI_VERTEX_OUTPUT_SHADER_HANDLE, - "ui_vertex_output.wgsl", - Shader::from_wgsl - ); - load_internal_asset!( - app, - UI_MATERIAL_SHADER_HANDLE, - "ui_material.wgsl", - Shader::from_wgsl - ); + load_shader_library!(app, "ui_vertex_output.wgsl"); + + embedded_asset!(app, "ui_material.wgsl"); + app.init_asset::() .register_type::>() .add_plugins(( @@ -75,13 +64,13 @@ where .init_resource::>>() .add_systems( ExtractSchedule, - extract_ui_material_nodes::.in_set(RenderUiSystem::ExtractBackgrounds), + extract_ui_material_nodes::.in_set(RenderUiSystems::ExtractBackgrounds), ) .add_systems( Render, ( - queue_ui_material_nodes::.in_set(RenderSet::Queue), - prepare_uimaterial_nodes::.in_set(RenderSet::PrepareBindGroups), + queue_ui_material_nodes::.in_set(RenderSystems::Queue), + prepare_uimaterial_nodes::.in_set(RenderSystems::PrepareBindGroups), ), ); } @@ -135,8 +124,8 @@ pub struct UiMaterialBatch { pub struct UiMaterialPipeline { pub ui_layout: BindGroupLayout, pub view_layout: BindGroupLayout, - pub vertex_shader: Option>, - pub fragment_shader: Option>, + pub vertex_shader: Handle, + pub fragment_shader: Handle, marker: PhantomData, } @@ -166,13 +155,13 @@ where let mut descriptor = RenderPipelineDescriptor { vertex: VertexState { - shader: UI_MATERIAL_SHADER_HANDLE, + shader: self.vertex_shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_layout], }, fragment: Some(FragmentState { - shader: UI_MATERIAL_SHADER_HANDLE, + shader: self.fragment_shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { @@ -205,13 +194,6 @@ where label: Some("ui_material_pipeline".into()), zero_initialize_workgroup_memory: false, }; - if let Some(vertex_shader) = &self.vertex_shader { - descriptor.vertex.shader = vertex_shader.clone(); - } - - if let Some(fragment_shader) = &self.fragment_shader { - descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); - } descriptor.layout = vec![self.view_layout.clone(), self.ui_layout.clone()]; @@ -238,18 +220,20 @@ impl FromWorld for UiMaterialPipeline { ), ); + let load_default = || load_embedded_asset!(asset_server, "ui_material.wgsl"); + UiMaterialPipeline { ui_layout, view_layout, vertex_shader: match M::vertex_shader() { - ShaderRef::Default => None, - ShaderRef::Handle(handle) => Some(handle), - ShaderRef::Path(path) => Some(asset_server.load(path)), + ShaderRef::Default => load_default(), + ShaderRef::Handle(handle) => handle, + ShaderRef::Path(path) => asset_server.load(path), }, fragment_shader: match M::fragment_shader() { - ShaderRef::Default => None, - ShaderRef::Handle(handle) => Some(handle), - ShaderRef::Path(path) => Some(asset_server.load(path)), + ShaderRef::Default => load_default(), + ShaderRef::Handle(handle) => handle, + ShaderRef::Path(path) => asset_server.load(path), }, marker: PhantomData, } diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index dee19ad867..80a55bbcd4 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ }; use bevy_image::prelude::*; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; -use bevy_platform_support::collections::HashMap; +use bevy_platform::collections::HashMap; use bevy_render::sync_world::MainEntity; use bevy_render::{ render_asset::RenderAssets, @@ -22,7 +22,7 @@ use bevy_render::{ sync_world::TemporaryRenderEntity, texture::{GpuImage, TRANSPARENT_IMAGE_HANDLE}, view::*, - Extract, ExtractSchedule, Render, RenderSet, + Extract, ExtractSchedule, Render, RenderSystems, }; use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer}; use bevy_transform::prelude::GlobalTransform; @@ -30,19 +30,11 @@ use binding_types::{sampler, texture_2d}; use bytemuck::{Pod, Zeroable}; use widget::ImageNode; -pub const UI_SLICER_SHADER_HANDLE: Handle = - weak_handle!("10cd61e3-bbf7-47fa-91c8-16cbe806378c"); - pub struct UiTextureSlicerPlugin; impl Plugin for UiTextureSlicerPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - UI_SLICER_SHADER_HANDLE, - "ui_texture_slice.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "ui_texture_slice.wgsl"); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -53,13 +45,13 @@ impl Plugin for UiTextureSlicerPlugin { .init_resource::>() .add_systems( ExtractSchedule, - extract_ui_texture_slices.in_set(RenderUiSystem::ExtractTextureSlice), + extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice), ) .add_systems( Render, ( - queue_ui_slices.in_set(RenderSet::Queue), - prepare_ui_slices.in_set(RenderSet::PrepareBindGroups), + queue_ui_slices.in_set(RenderSystems::Queue), + prepare_ui_slices.in_set(RenderSystems::PrepareBindGroups), ), ); } @@ -116,6 +108,7 @@ pub struct UiTextureSliceImageBindGroups { pub struct UiTextureSlicePipeline { pub view_layout: BindGroupLayout, pub image_layout: BindGroupLayout, + pub shader: Handle, } impl FromWorld for UiTextureSlicePipeline { @@ -144,6 +137,7 @@ impl FromWorld for UiTextureSlicePipeline { UiTextureSlicePipeline { view_layout, image_layout, + shader: load_embedded_asset!(world, "ui_texture_slice.wgsl"), } } } @@ -180,13 +174,13 @@ impl SpecializedRenderPipeline for UiTextureSlicePipeline { RenderPipelineDescriptor { vertex: VertexState { - shader: UI_SLICER_SHADER_HANDLE, + shader: self.shader.clone(), entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_layout], }, fragment: Some(FragmentState { - shader: UI_SLICER_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index c8dfde318d..d50311cbce 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -1,7 +1,7 @@ //! This module contains the systems that update the stored UI nodes stack use bevy_ecs::prelude::*; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use crate::{ experimental::{UiChildren, UiRootNodes}, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 2486296bac..4592b091d9 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,5 +1,5 @@ use crate::{FocusPolicy, UiRect, Val}; -use bevy_color::Color; +use bevy_color::{Alpha, Color}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, system::SystemParam}; use bevy_math::{vec4, Rect, UVec2, Vec2, Vec4Swizzles}; @@ -13,7 +13,7 @@ use bevy_sprite::BorderRect; use bevy_transform::components::Transform; use bevy_utils::once; use bevy_window::{PrimaryWindow, WindowRef}; -use core::num::NonZero; +use core::{f32, num::NonZero}; use derive_more::derive::From; use smallvec::SmallVec; use thiserror::Error; @@ -31,7 +31,7 @@ pub struct ComputedNode { /// The order of the node in the UI layout. /// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices. /// - /// Automatically calculated in [`super::UiSystem::Stack`]. + /// Automatically calculated in [`super::UiSystems::Stack`]. pub stack_index: u32, /// The size of the node as width and height in physical pixels. /// @@ -162,8 +162,8 @@ impl ComputedNode { ResolvedBorderRadius { top_left: compute_radius(self.border_radius.top_left, outer_distance), top_right: compute_radius(self.border_radius.top_right, outer_distance), - bottom_left: compute_radius(self.border_radius.bottom_left, outer_distance), bottom_right: compute_radius(self.border_radius.bottom_right, outer_distance), + bottom_left: compute_radius(self.border_radius.bottom_left, outer_distance), } } @@ -200,8 +200,8 @@ impl ComputedNode { ResolvedBorderRadius { top_left: clamp_corner(self.border_radius.top_left, s, b.xy()), top_right: clamp_corner(self.border_radius.top_right, s, b.zy()), - bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()), bottom_right: clamp_corner(self.border_radius.bottom_left, s, b.xw()), + bottom_left: clamp_corner(self.border_radius.bottom_right, s, b.zw()), } } @@ -1178,7 +1178,7 @@ pub struct OverflowClipMargin { impl OverflowClipMargin { pub const DEFAULT: Self = Self { - visual_box: OverflowClipBox::ContentBox, + visual_box: OverflowClipBox::PaddingBox, margin: 0., }; @@ -1224,9 +1224,9 @@ impl OverflowClipMargin { )] pub enum OverflowClipBox { /// Clip any content that overflows outside the content box - #[default] ContentBox, /// Clip any content that overflows outside the padding box + #[default] PaddingBox, /// Clip any content that overflows outside the border box BorderBox, @@ -2036,17 +2036,40 @@ impl> From for BackgroundColor { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -pub struct BorderColor(pub Color); +pub struct BorderColor { + pub top: Color, + pub right: Color, + pub bottom: Color, + pub left: Color, +} impl> From for BorderColor { fn from(color: T) -> Self { - Self(color.into()) + Self::all(color.into()) } } impl BorderColor { /// Border color is transparent by default. - pub const DEFAULT: Self = BorderColor(Color::NONE); + pub const DEFAULT: Self = BorderColor::all(Color::NONE); + + /// Helper to create a `BorderColor` struct with all borders set to the given color + pub const fn all(color: Color) -> Self { + Self { + top: color, + bottom: color, + left: color, + right: color, + } + } + + /// Check if all contained border colors are transparent + pub fn is_fully_transparent(&self) -> bool { + self.top.is_fully_transparent() + && self.bottom.is_fully_transparent() + && self.left.is_fully_transparent() + && self.right.is_fully_transparent() + } } impl Default for BorderColor { @@ -2217,8 +2240,8 @@ pub struct GlobalZIndex(pub i32); pub struct BorderRadius { pub top_left: Val, pub top_right: Val, - pub bottom_left: Val, pub bottom_right: Val, + pub bottom_left: Val, } impl Default for BorderRadius { @@ -2430,56 +2453,52 @@ impl BorderRadius { /// Resolve the border radius for a single corner from the given context values. /// Returns the radius of the corner in physical pixels. - pub fn resolve_single_corner( + pub const fn resolve_single_corner( radius: Val, - node_size: Vec2, - viewport_size: Vec2, scale_factor: f32, + min_length: f32, + viewport_size: Vec2, ) -> f32 { - match radius { - Val::Auto => 0., - Val::Px(px) => px * scale_factor, - Val::Percent(percent) => node_size.min_element() * percent / 100., - Val::Vw(percent) => viewport_size.x * percent / 100., - Val::Vh(percent) => viewport_size.y * percent / 100., - Val::VMin(percent) => viewport_size.min_element() * percent / 100., - Val::VMax(percent) => viewport_size.max_element() * percent / 100., + if let Ok(radius) = radius.resolve(scale_factor, min_length, viewport_size) { + radius.clamp(0., 0.5 * min_length) + } else { + 0. } - .clamp(0., 0.5 * node_size.min_element()) } /// Resolve the border radii for the corners from the given context values. /// Returns the radii of the each corner in physical pixels. - pub fn resolve( + pub const fn resolve( &self, + scale_factor: f32, node_size: Vec2, viewport_size: Vec2, - scale_factor: f32, ) -> ResolvedBorderRadius { + let length = node_size.x.min(node_size.y); ResolvedBorderRadius { top_left: Self::resolve_single_corner( self.top_left, - node_size, - viewport_size, scale_factor, + length, + viewport_size, ), top_right: Self::resolve_single_corner( self.top_right, - node_size, - viewport_size, scale_factor, + length, + viewport_size, ), bottom_left: Self::resolve_single_corner( self.bottom_left, - node_size, - viewport_size, scale_factor, + length, + viewport_size, ), bottom_right: Self::resolve_single_corner( self.bottom_right, - node_size, - viewport_size, scale_factor, + length, + viewport_size, ), } } @@ -2493,16 +2512,16 @@ impl BorderRadius { pub struct ResolvedBorderRadius { pub top_left: f32, pub top_right: f32, - pub bottom_left: f32, pub bottom_right: f32, + pub bottom_left: f32, } impl ResolvedBorderRadius { pub const ZERO: Self = Self { top_left: 0., top_right: 0., - bottom_left: 0., bottom_right: 0., + bottom_left: 0., }; } @@ -2600,37 +2619,6 @@ impl Default for LayoutConfig { } } -#[cfg(test)] -mod tests { - use crate::GridPlacement; - - #[test] - fn invalid_grid_placement_values() { - assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err()); - assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err()); - } - - #[test] - fn grid_placement_accessors() { - assert_eq!(GridPlacement::start(5).get_start(), Some(5)); - assert_eq!(GridPlacement::end(-4).get_end(), Some(-4)); - assert_eq!(GridPlacement::span(2).get_span(), Some(2)); - assert_eq!(GridPlacement::start_end(11, 21).get_span(), None); - assert_eq!(GridPlacement::start_span(3, 5).get_end(), None); - assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None); - } -} - /// Indicates that this root [`Node`] entity should be rendered to a specific camera. /// /// UI then will be laid out respecting the camera's viewport and scale factor, and @@ -2828,3 +2816,34 @@ impl Default for TextShadow { } } } + +#[cfg(test)] +mod tests { + use crate::GridPlacement; + + #[test] + fn invalid_grid_placement_values() { + assert!(std::panic::catch_unwind(|| GridPlacement::span(0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::start(0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::end(0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::start_end(0, 1)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::start_end(-1, 0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::start_span(1, 0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::start_span(0, 1)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::end_span(0, 1)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::end_span(1, 0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::default().set_start(0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::default().set_end(0)).is_err()); + assert!(std::panic::catch_unwind(|| GridPlacement::default().set_span(0)).is_err()); + } + + #[test] + fn grid_placement_accessors() { + assert_eq!(GridPlacement::start(5).get_start(), Some(5)); + assert_eq!(GridPlacement::end(-4).get_end(), Some(-4)); + assert_eq!(GridPlacement::span(2).get_span(), Some(2)); + assert_eq!(GridPlacement::start_end(11, 21).get_span(), None); + assert_eq!(GridPlacement::start_span(3, 5).get_end(), None); + assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None); + } +} diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 67d5947547..7e27c4abdd 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -9,10 +9,10 @@ use crate::{ use super::ComputedNode; use bevy_ecs::{ change_detection::DetectChangesMut, - entity::{hash_set::EntityHashSet, Entity}, + entity::Entity, hierarchy::ChildOf, query::{Changed, With}, - system::{Commands, Local, Query, Res}, + system::{Commands, Query, Res}, }; use bevy_math::{Rect, UVec2}; use bevy_render::camera::Camera; @@ -139,9 +139,7 @@ pub fn update_ui_context_system( mut computed_target_query: Query<&mut ComputedNodeTarget>, ui_children: UiChildren, reparented_nodes: Query<(Entity, &ChildOf), (Changed, With)>, - mut visited: Local, ) { - visited.clear(); let default_camera_entity = default_ui_camera.get(); for root_entity in ui_root_nodes.iter() { @@ -172,12 +170,11 @@ pub fn update_ui_context_system( }, &ui_children, &mut computed_target_query, - &mut visited, ); } for (entity, child_of) in reparented_nodes.iter() { - let Ok(computed_target) = computed_target_query.get(child_of.parent) else { + let Ok(computed_target) = computed_target_query.get(child_of.parent()) else { continue; }; @@ -186,7 +183,6 @@ pub fn update_ui_context_system( *computed_target, &ui_children, &mut computed_target_query, - &mut visited, ); } } @@ -196,24 +192,14 @@ fn update_contexts_recursively( inherited_computed_target: ComputedNodeTarget, ui_children: &UiChildren, query: &mut Query<&mut ComputedNodeTarget>, - visited: &mut EntityHashSet, ) { - if !visited.insert(entity) { - return; - } if query .get_mut(entity) .map(|mut computed_target| computed_target.set_if_neq(inherited_computed_target)) .unwrap_or(false) { for child in ui_children.iter_ui_children(entity) { - update_contexts_recursively( - child, - inherited_computed_target, - ui_children, - query, - visited, - ); + update_contexts_recursively(child, inherited_computed_target, ui_children, query); } } } diff --git a/crates/bevy_ui/src/widget/button.rs b/crates/bevy_ui/src/widget/button.rs index e793c968b5..abb788f21b 100644 --- a/crates/bevy_ui/src/widget/button.rs +++ b/crates/bevy_ui/src/widget/button.rs @@ -5,5 +5,5 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// Marker struct for buttons #[derive(Component, Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)] #[reflect(Component, Default, Debug, PartialEq, Clone)] -#[require(Node, FocusPolicy(|| FocusPolicy::Block), Interaction)] +#[require(Node, FocusPolicy::Block, Interaction)] pub struct Button; diff --git a/crates/bevy_ui/src/widget/mod.rs b/crates/bevy_ui/src/widget/mod.rs index 9be6a7673d..bbd319e986 100644 --- a/crates/bevy_ui/src/widget/mod.rs +++ b/crates/bevy_ui/src/widget/mod.rs @@ -3,11 +3,11 @@ mod button; mod image; mod label; - mod text; +mod viewport; pub use button::*; pub use image::*; pub use label::*; - pub use text::*; +pub use viewport::*; diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 0153fa954c..785040c1e9 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -20,7 +20,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_text::{ scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache, TextBounds, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextMeasureInfo, - TextPipeline, TextReader, TextRoot, TextSpanAccess, TextWriter, YAxisOrientation, + TextPipeline, TextReader, TextRoot, TextSpanAccess, TextWriter, }; use taffy::style::AvailableSpace; use tracing::error; @@ -328,7 +328,6 @@ fn queue_text( font_atlas_sets, texture_atlases, textures, - YAxisOrientation::TopToBottom, computed, font_system, swash_cache, diff --git a/crates/bevy_ui/src/widget/viewport.rs b/crates/bevy_ui/src/widget/viewport.rs new file mode 100644 index 0000000000..f68033ea7f --- /dev/null +++ b/crates/bevy_ui/src/widget/viewport.rs @@ -0,0 +1,181 @@ +use bevy_asset::Assets; +use bevy_ecs::{ + component::Component, + entity::Entity, + event::EventReader, + query::{Changed, Or}, + reflect::ReflectComponent, + system::{Commands, Query, Res, ResMut}, +}; +use bevy_image::Image; +use bevy_math::Rect; +#[cfg(feature = "bevy_ui_picking_backend")] +use bevy_picking::{ + events::PointerState, + hover::HoverMap, + pointer::{Location, PointerId, PointerInput, PointerLocation}, +}; +use bevy_platform::collections::HashMap; +use bevy_reflect::Reflect; +use bevy_render::{ + camera::{Camera, NormalizedRenderTarget}, + render_resource::Extent3d, +}; +use bevy_transform::components::GlobalTransform; +use bevy_utils::default; +#[cfg(feature = "bevy_ui_picking_backend")] +use uuid::Uuid; + +use crate::{ComputedNode, Node}; + +/// Component used to render a [`Camera::target`] to a node. +/// +/// # See Also +/// +/// [`update_viewport_render_target_size`] +#[derive(Component, Debug, Clone, Copy, Reflect)] +#[reflect(Component, Debug)] +#[require(Node)] +#[cfg_attr( + feature = "bevy_ui_picking_backend", + require(PointerId::Custom(Uuid::new_v4())) +)] +pub struct ViewportNode { + /// The entity representing the [`Camera`] associated with this viewport. + /// + /// Note that removing the [`ViewportNode`] component will not despawn this entity. + pub camera: Entity, +} + +impl ViewportNode { + /// Creates a new [`ViewportNode`] with a given `camera`. + pub fn new(camera: Entity) -> Self { + Self { camera } + } +} + +#[cfg(feature = "bevy_ui_picking_backend")] +/// Handles viewport picking logic. +/// +/// Viewport entities that are being hovered or dragged will have all pointer inputs sent to them. +pub fn viewport_picking( + mut commands: Commands, + mut viewport_query: Query<( + Entity, + &ViewportNode, + &PointerId, + &mut PointerLocation, + &ComputedNode, + &GlobalTransform, + )>, + camera_query: Query<&Camera>, + hover_map: Res, + pointer_state: Res, + mut pointer_inputs: EventReader, +) { + // Handle hovered entities. + let mut viewport_picks: HashMap = hover_map + .iter() + .flat_map(|(hover_pointer_id, hits)| { + hits.iter() + .filter(|(entity, _)| viewport_query.contains(**entity)) + .map(|(entity, _)| (*entity, *hover_pointer_id)) + }) + .collect(); + + // Handle dragged entities, which need to be considered for dragging in and out of viewports. + for ((pointer_id, _), pointer_state) in pointer_state.pointer_buttons.iter() { + for &target in pointer_state + .dragging + .keys() + .filter(|&entity| viewport_query.contains(*entity)) + { + viewport_picks.insert(target, *pointer_id); + } + } + + for ( + viewport_entity, + &viewport, + &viewport_pointer_id, + mut viewport_pointer_location, + computed_node, + global_transform, + ) in &mut viewport_query + { + let Some(pick_pointer_id) = viewport_picks.get(&viewport_entity) else { + // Lift the viewport pointer if it's not being used. + viewport_pointer_location.location = None; + continue; + }; + let Ok(camera) = camera_query.get(viewport.camera) else { + continue; + }; + let Some(cam_viewport_size) = camera.logical_viewport_size() else { + continue; + }; + + // Create a `Rect` in *physical* coordinates centered at the node's GlobalTransform + let node_rect = Rect::from_center_size( + global_transform.translation().truncate(), + computed_node.size(), + ); + // Location::position uses *logical* coordinates + let top_left = node_rect.min * computed_node.inverse_scale_factor(); + let logical_size = computed_node.size() * computed_node.inverse_scale_factor(); + + let Some(target) = camera.target.as_image() else { + continue; + }; + + for input in pointer_inputs + .read() + .filter(|input| &input.pointer_id == pick_pointer_id) + { + let local_position = (input.location.position - top_left) / logical_size; + let position = local_position * cam_viewport_size; + + let location = Location { + position, + target: NormalizedRenderTarget::Image(target.clone().into()), + }; + viewport_pointer_location.location = Some(location.clone()); + + commands.send_event(PointerInput { + location, + pointer_id: viewport_pointer_id, + action: input.action, + }); + } + } +} + +/// Updates the size of the associated render target for viewports when the node size changes. +pub fn update_viewport_render_target_size( + viewport_query: Query< + (&ViewportNode, &ComputedNode), + Or<(Changed, Changed)>, + >, + camera_query: Query<&Camera>, + mut images: ResMut>, +) { + for (viewport, computed_node) in &viewport_query { + let camera = camera_query.get(viewport.camera).unwrap(); + let size = computed_node.size(); + + let Some(image_handle) = camera.target.as_image() else { + continue; + }; + let size = Extent3d { + width: u32::max(1, size.x as u32), + height: u32::max(1, size.y as u32), + ..default() + }; + let image = images.get_mut(image_handle).unwrap(); + if image.data.is_some() { + image.resize(size); + } else { + image.texture_descriptor.size = size; + } + } +} diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 2e8e9dc314..ad3e1ae9c7 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -9,32 +9,21 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["std", "serde"] +default = ["parallel"] -# Functionality +wgpu_wrapper = ["dep:send_wrapper"] -## Adds serialization support through `serde`. -serde = ["bevy_platform_support/serialize"] - -# Platform Compatibility - -## Allows access to the `std` crate. Enabling this feature will prevent compilation -## on `no_std` targets, but provides access to certain additional features on -## supported platforms. -std = ["alloc", "bevy_platform_support/std", "dep:thread_local"] - -## Allows access to the `alloc` crate. -alloc = ["bevy_platform_support/alloc"] - -## `critical-section` provides the building blocks for synchronization primitives -## on all platforms, including `no_std`. -critical-section = ["bevy_platform_support/critical-section"] +# Provides access to the `Parallel` type. +parallel = ["bevy_platform/std", "dep:thread_local"] [dependencies] -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } thread_local = { version = "1.0", optional = true } +[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies] +send_wrapper = { version = "0.6.0", optional = true } + [dev-dependencies] static_assertions = "1.1.0" diff --git a/crates/bevy_utils/src/default.rs b/crates/bevy_utils/src/default.rs index 5b4b9fbdf9..0ca45d544d 100644 --- a/crates/bevy_utils/src/default.rs +++ b/crates/bevy_utils/src/default.rs @@ -12,7 +12,7 @@ /// } /// /// // Normally you would initialize a struct with defaults using "struct update syntax" -/// // combined with `Default::default()`. This example sets `Foo::bar` to 10 and the remaining +/// // combined with `Default::default()`. This example sets `Foo::a` to 10 and the remaining /// // values to their defaults. /// let foo = Foo { /// a: 10, diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 4b7d91a40e..164610eb9b 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -9,11 +9,35 @@ //! //! [Bevy]: https://bevyengine.org/ -#[cfg(feature = "std")] -extern crate std; +/// Configuration information for this crate. +pub mod cfg { + pub(crate) use bevy_platform::cfg::*; -#[cfg(feature = "alloc")] -extern crate alloc; + pub use bevy_platform::cfg::{alloc, std}; + + define_alias! { + #[cfg(feature = "parallel")] => { + /// Indicates the `Parallel` type is available. + parallel + } + } +} + +cfg::std! { + extern crate std; +} + +cfg::alloc! { + extern crate alloc; + + mod map; + pub use map::*; +} + +cfg::parallel! { + mod parallel_queue; + pub use parallel_queue::*; +} /// The utilities prelude. /// @@ -22,70 +46,22 @@ pub mod prelude { pub use crate::default; } -pub mod synccell; -pub mod syncunsafecell; +#[cfg(feature = "wgpu_wrapper")] +mod wgpu_wrapper; mod default; mod once; -#[cfg(feature = "std")] -mod parallel_queue; #[doc(hidden)] pub use once::OnceFlag; pub use default::default; -#[cfg(feature = "std")] -pub use parallel_queue::*; +#[cfg(feature = "wgpu_wrapper")] +pub use wgpu_wrapper::WgpuWrapper; use core::mem::ManuallyDrop; -#[cfg(feature = "alloc")] -use { - bevy_platform_support::{ - collections::HashMap, - hash::{Hashed, NoOpHash, PassHash}, - }, - core::{any::TypeId, hash::Hash}, -}; - -/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing. -/// Iteration order only depends on the order of insertions and deletions. -#[cfg(feature = "alloc")] -pub type PreHashMap = HashMap, V, PassHash>; - -/// Extension methods intended to add functionality to [`PreHashMap`]. -#[cfg(feature = "alloc")] -pub trait PreHashMapExt { - /// Tries to get or insert the value for the given `key` using the pre-computed hash first. - /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert - /// the value returned by `func`. - fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; -} - -#[cfg(feature = "alloc")] -impl PreHashMapExt for PreHashMap { - #[inline] - fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V { - use bevy_platform_support::collections::hash_map::RawEntryMut; - let entry = self - .raw_entry_mut() - .from_key_hashed_nocheck(key.hash(), key); - match entry { - RawEntryMut::Occupied(entry) => entry.into_mut(), - RawEntryMut::Vacant(entry) => { - let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func()); - value - } - } - } -} - -/// A specialized hashmap type with Key of [`TypeId`] -/// Iteration order only depends on the order of insertions and deletions. -#[cfg(feature = "alloc")] -pub type TypeIdMap = HashMap; - /// A type which calls a function when dropped. /// This can be used to ensure that cleanup code is run even in case of a panic. /// @@ -144,46 +120,3 @@ impl Drop for OnDrop { callback(); } } - -#[cfg(test)] -mod tests { - use super::*; - use static_assertions::assert_impl_all; - - // Check that the HashMaps are Clone if the key/values are Clone - assert_impl_all!(PreHashMap::: Clone); - - #[test] - fn fast_typeid_hash() { - struct Hasher; - - impl core::hash::Hasher for Hasher { - fn finish(&self) -> u64 { - 0 - } - fn write(&mut self, _: &[u8]) { - panic!("Hashing of core::any::TypeId changed"); - } - fn write_u64(&mut self, _: u64) {} - } - - Hash::hash(&TypeId::of::<()>(), &mut Hasher); - } - - #[cfg(feature = "alloc")] - #[test] - fn stable_hash_within_same_program_execution() { - use alloc::vec::Vec; - - let mut map_1 = >::default(); - let mut map_2 = >::default(); - for i in 1..10 { - map_1.insert(i, i); - map_2.insert(i, i); - } - assert_eq!( - map_1.iter().collect::>(), - map_2.iter().collect::>() - ); - } -} diff --git a/crates/bevy_utils/src/map.rs b/crates/bevy_utils/src/map.rs new file mode 100644 index 0000000000..ca74e34dbb --- /dev/null +++ b/crates/bevy_utils/src/map.rs @@ -0,0 +1,83 @@ +use core::{any::TypeId, hash::Hash}; + +use bevy_platform::{ + collections::HashMap, + hash::{Hashed, NoOpHash, PassHash}, +}; + +/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing. +/// Iteration order only depends on the order of insertions and deletions. +pub type PreHashMap = HashMap, V, PassHash>; + +/// Extension methods intended to add functionality to [`PreHashMap`]. +pub trait PreHashMapExt { + /// Tries to get or insert the value for the given `key` using the pre-computed hash first. + /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert + /// the value returned by `func`. + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; +} + +impl PreHashMapExt for PreHashMap { + #[inline] + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V { + use bevy_platform::collections::hash_map::RawEntryMut; + let entry = self + .raw_entry_mut() + .from_key_hashed_nocheck(key.hash(), key); + match entry { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func()); + value + } + } + } +} + +/// A specialized hashmap type with Key of [`TypeId`] +/// Iteration order only depends on the order of insertions and deletions. +pub type TypeIdMap = HashMap; + +#[cfg(test)] +mod tests { + use super::*; + use static_assertions::assert_impl_all; + + // Check that the HashMaps are Clone if the key/values are Clone + assert_impl_all!(PreHashMap::: Clone); + + #[test] + fn fast_typeid_hash() { + struct Hasher; + + impl core::hash::Hasher for Hasher { + fn finish(&self) -> u64 { + 0 + } + fn write(&mut self, _: &[u8]) { + panic!("Hashing of core::any::TypeId changed"); + } + fn write_u64(&mut self, _: u64) {} + } + + Hash::hash(&TypeId::of::<()>(), &mut Hasher); + } + + crate::cfg::alloc! { + #[test] + fn stable_hash_within_same_program_execution() { + use alloc::vec::Vec; + + let mut map_1 = >::default(); + let mut map_2 = >::default(); + for i in 1..10 { + map_1.insert(i, i); + map_2.insert(i, i); + } + assert_eq!( + map_1.iter().collect::>(), + map_2.iter().collect::>() + ); + } + } +} \ No newline at end of file diff --git a/crates/bevy_utils/src/once.rs b/crates/bevy_utils/src/once.rs index 674926ec53..f36b1077a2 100644 --- a/crates/bevy_utils/src/once.rs +++ b/crates/bevy_utils/src/once.rs @@ -1,4 +1,4 @@ -use bevy_platform_support::sync::atomic::{AtomicBool, Ordering}; +use bevy_platform::sync::atomic::{AtomicBool, Ordering}; /// Wrapper around an [`AtomicBool`], abstracting the backing implementation and /// ordering considerations. diff --git a/crates/bevy_utils/src/parallel_queue.rs b/crates/bevy_utils/src/parallel_queue.rs index f9c4c66ca0..e97a48378d 100644 --- a/crates/bevy_utils/src/parallel_queue.rs +++ b/crates/bevy_utils/src/parallel_queue.rs @@ -10,7 +10,6 @@ pub struct Parallel { locals: ThreadLocal>, } -/// A scope guard of a `Parallel`, when this struct is dropped ,the value will writeback to its `Parallel` impl Parallel { /// Gets a mutable iterator over all of the per-thread queues. pub fn iter_mut(&mut self) -> impl Iterator { @@ -29,13 +28,12 @@ impl Parallel { /// If there is no thread-local value, it will be initialized to its default. pub fn scope(&self, f: impl FnOnce(&mut T) -> R) -> R { let mut cell = self.locals.get_or_default().borrow_mut(); - let ret = f(cell.deref_mut()); - ret + f(cell.deref_mut()) } /// Mutably borrows the thread-local value. /// - /// If there is no thread-local value, it will be initialized to it's default. + /// If there is no thread-local value, it will be initialized to its default. pub fn borrow_local_mut(&self) -> impl DerefMut + '_ { self.locals.get_or_default().borrow_mut() } @@ -57,7 +55,6 @@ where } } -#[cfg(feature = "alloc")] impl Parallel> { /// Collect all enqueued items from all threads and appends them to the end of a /// single Vec. diff --git a/crates/bevy_utils/src/wgpu_wrapper.rs b/crates/bevy_utils/src/wgpu_wrapper.rs new file mode 100644 index 0000000000..272d0dd4c0 --- /dev/null +++ b/crates/bevy_utils/src/wgpu_wrapper.rs @@ -0,0 +1,50 @@ +/// A wrapper to safely make `wgpu` types Send / Sync on web with atomics enabled. +/// +/// On web with `atomics` enabled the inner value can only be accessed +/// or dropped on the `wgpu` thread or else a panic will occur. +/// On other platforms the wrapper simply contains the wrapped value. +#[derive(Debug, Clone)] +pub struct WgpuWrapper( + #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] T, + #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] send_wrapper::SendWrapper, +); + +// SAFETY: SendWrapper is always Send + Sync. +#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] +#[expect(unsafe_code, reason = "Blanket-impl Send requires unsafe.")] +unsafe impl Send for WgpuWrapper {} +#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] +#[expect(unsafe_code, reason = "Blanket-impl Sync requires unsafe.")] +unsafe impl Sync for WgpuWrapper {} + +impl WgpuWrapper { + /// Constructs a new instance of `WgpuWrapper` which will wrap the specified value. + pub fn new(t: T) -> Self { + #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] + return Self(t); + #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] + return Self(send_wrapper::SendWrapper::new(t)); + } + + /// Unwraps the value. + pub fn into_inner(self) -> T { + #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] + return self.0; + #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] + return self.0.take(); + } +} + +impl core::ops::Deref for WgpuWrapper { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for WgpuWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 2920c88361..b2b6d730fe 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -42,7 +42,7 @@ std = [ "bevy_reflect?/std", "serde?/std", "raw-window-handle/std", - "bevy_platform_support/std", + "bevy_platform/std", ] ## Uses the `libm` maths library instead of the one provided in `std` and `core`. @@ -59,7 +59,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu "smol_str", ], optional = true } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false } # other serde = { version = "1.0", features = [ diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 851c8a681f..21ee8d64c9 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -19,7 +19,7 @@ extern crate alloc; use alloc::sync::Arc; -use bevy_platform_support::sync::Mutex; +use bevy_platform::sync::Mutex; mod event; mod monitor; @@ -177,11 +177,11 @@ impl Plugin for WindowPlugin { pub enum ExitCondition { /// Close application when the primary window is closed /// - /// The plugin will add [`exit_on_primary_closed`] to [`Update`]. + /// The plugin will add [`exit_on_primary_closed`] to [`PostUpdate`]. OnPrimaryClosed, /// Close application when all windows are closed /// - /// The plugin will add [`exit_on_all_closed`] to [`Update`]. + /// The plugin will add [`exit_on_all_closed`] to [`PostUpdate`]. OnAllClosed, /// Keep application running headless even after closing all windows /// diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 3894eb283e..0943315055 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -5,7 +5,7 @@ use alloc::sync::Arc; use bevy_ecs::prelude::Component; -use bevy_platform_support::sync::Mutex; +use bevy_platform::sync::Mutex; use core::{any::Any, marker::PhantomData, ops::Deref}; use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, @@ -50,6 +50,10 @@ impl Deref for WindowWrapper { /// thread-safe. #[derive(Debug, Clone, Component)] pub struct RawHandleWrapper { + /// A shared reference to the window. + /// This allows us to extend the lifetime of the window, + /// so it doesn’t get eagerly dropped while a pipelined + /// renderer still has frames in flight that need to draw to it. _window: Arc, /// Raw handle to a window. window_handle: RawWindowHandle, diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 0414eb5098..4f7d7b2f7b 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,11 +1,12 @@ -use alloc::{borrow::ToOwned, string::String}; +use alloc::{borrow::ToOwned, format, string::String}; use core::num::NonZero; use bevy_ecs::{ - entity::{Entity, EntityBorrow, VisitEntities, VisitEntitiesMut}, + entity::{ContainsEntity, Entity}, prelude::Component, }; use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2}; +use bevy_platform::sync::LazyLock; use log::warn; #[cfg(feature = "bevy_reflect")] @@ -19,6 +20,24 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use crate::VideoMode; +/// Default string used for the window title. +/// +/// It will try to use the name of the current exe if possible, otherwise it defaults to "App" +static DEFAULT_WINDOW_TITLE: LazyLock = LazyLock::new(|| { + #[cfg(feature = "std")] + { + std::env::current_exe() + .ok() + .and_then(|current_exe| Some(format!("{}", current_exe.file_stem()?.to_string_lossy()))) + .unwrap_or_else(|| "App".to_owned()) + } + + #[cfg(not(feature = "std"))] + { + "App".to_owned() + } +}); + /// Marker [`Component`] for the window considered the primary window. /// /// Currently this is assumed to only exist on 1 entity at a time. @@ -74,24 +93,6 @@ impl WindowRef { } } -impl VisitEntities for WindowRef { - fn visit_entities(&self, mut f: F) { - match self { - Self::Entity(entity) => f(*entity), - Self::Primary => {} - } - } -} - -impl VisitEntitiesMut for WindowRef { - fn visit_entities_mut(&mut self, mut f: F) { - match self { - Self::Entity(entity) => f(entity), - Self::Primary => {} - } - } -} - /// A flattened representation of a window reference for equality/hashing purposes. /// /// For most purposes you probably want to use the unnormalized version [`WindowRef`]. @@ -109,7 +110,7 @@ impl VisitEntitiesMut for WindowRef { )] pub struct NormalizedWindowRef(Entity); -impl EntityBorrow for NormalizedWindowRef { +impl ContainsEntity for NormalizedWindowRef { fn entity(&self) -> Entity { self.0 } @@ -224,6 +225,15 @@ pub struct Window { /// You should also set the window `composite_alpha_mode` to `CompositeAlphaMode::PostMultiplied`. pub transparent: bool, /// Get/set whether the window is focused. + /// + /// It cannot be set unfocused after creation. + /// + /// ## Platform-specific + /// + /// - iOS / Android / X11 / Wayland: Spawning unfocused is + /// [not supported](https://docs.rs/winit/latest/winit/window/struct.WindowAttributes.html#method.with_active). + /// - iOS / Android / Web / Wayland: Setting focused after creation is + /// [not supported](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.focus_window). pub focused: bool, /// Where should the window appear relative to other overlapping window. /// @@ -442,12 +452,23 @@ pub struct Window { /// /// [`WindowAttributesExtIOS::with_prefers_status_bar_hidden`]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/ios/trait.WindowAttributesExtIOS.html#tymethod.with_prefers_status_bar_hidden pub prefers_status_bar_hidden: bool, + /// Sets screen edges for which you want your gestures to take precedence + /// over the system gestures. + /// + /// Corresponds to [`WindowAttributesExtIOS::with_preferred_screen_edges_deferring_system_gestures`]. + /// + /// # Platform-specific + /// + /// - Only used on iOS. + /// + /// [`WindowAttributesExtIOS::with_preferred_screen_edges_deferring_system_gestures`]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/ios/trait.WindowAttributesExtIOS.html#tymethod.with_preferred_screen_edges_deferring_system_gestures + pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } impl Default for Window { fn default() -> Self { Self { - title: "App".to_owned(), + title: DEFAULT_WINDOW_TITLE.to_owned(), name: None, cursor_options: Default::default(), present_mode: Default::default(), @@ -486,6 +507,7 @@ impl Default for Window { titlebar_show_buttons: true, prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, + preferred_screen_edges_deferring_system_gestures: Default::default(), } } } @@ -1443,6 +1465,31 @@ impl Default for EnabledButtons { #[derive(Component, Default)] pub struct ClosingWindow; +/// The edges of a screen. Corresponds to [`winit::platform::ios::ScreenEdge`]. +/// +/// # Platform-specific +/// +/// - Only used on iOS. +/// +/// [`winit::platform::ios::ScreenEdge`]: https://docs.rs/winit/latest/x86_64-apple-darwin/winit/platform/ios/struct.ScreenEdge.html +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub enum ScreenEdge { + #[default] + /// No edge. + None, + /// The top edge of the screen. + Top, + /// The left edge of the screen. + Left, + /// The bottom edge of the screen. + Bottom, + /// The right edge of the screen. + Right, + /// All edges of the screen. + All, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index ff888332ab..341afa4f60 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] +default = ["x11"] trace = [] wayland = ["winit/wayland", "winit/wayland-csd-adwaita"] x11 = ["winit/x11"] @@ -18,7 +19,7 @@ serialize = [ "serde", "bevy_input/serialize", "bevy_window/serialize", - "bevy_platform_support/serialize", + "bevy_platform/serialize", ] android-native-activity = ["winit/android-native-activity"] android-game-activity = ["winit/android-game-activity"] @@ -39,7 +40,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_window = { path = "../bevy_window", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "std", ] } @@ -50,7 +51,7 @@ bevy_image = { path = "../bevy_image", version = "0.16.0-dev", optional = true } # other # feature rwh_06 refers to window_raw_handle@v0.6 winit = { version = "0.30", default-features = false, features = ["rwh_06"] } -accesskit_winit = { version = "0.23", default-features = false, features = [ +accesskit_winit = { version = "0.27", default-features = false, features = [ "rwh_06", ] } approx = { version = "0.5", default-features = false } @@ -59,7 +60,7 @@ raw-window-handle = "0.6" serde = { version = "1.0", features = ["derive"], optional = true } bytemuck = { version = "1.5", optional = true } wgpu-types = { version = "24", optional = true } -accesskit = "0.17" +accesskit = "0.19" tracing = { version = "0.1", default-features = false, features = ["std"] } [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -73,7 +74,7 @@ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = fa bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [ "web", ] } -bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ +bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [ "web", ] } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [ diff --git a/crates/bevy_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index c2545d249a..b14fd4f57f 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -2,7 +2,9 @@ use alloc::{collections::VecDeque, sync::Arc}; use bevy_input_focus::InputFocus; +use core::cell::RefCell; use std::sync::Mutex; +use winit::event_loop::ActiveEventLoop; use accesskit::{ ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, Role, Tree, @@ -10,18 +12,31 @@ use accesskit::{ }; use accesskit_winit::Adapter; use bevy_a11y::{ - AccessibilityNode, AccessibilityRequested, AccessibilitySystem, + AccessibilityNode, AccessibilityRequested, AccessibilitySystems, ActionRequest as ActionRequestWrapper, ManageAccessibilityUpdates, }; use bevy_app::{App, Plugin, PostUpdate}; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{entity::hash_map::EntityHashMap, prelude::*}; +use bevy_ecs::{entity::EntityHashMap, prelude::*, system::NonSendMarker}; use bevy_window::{PrimaryWindow, Window, WindowClosed}; +thread_local! { + /// Temporary storage of access kit adapter data to replace usage of `!Send` resources. This will be replaced with proper + /// storage of `!Send` data after issue #17667 is complete. + pub static ACCESS_KIT_ADAPTERS: RefCell = const { RefCell::new(AccessKitAdapters::new()) }; +} + /// Maps window entities to their `AccessKit` [`Adapter`]s. #[derive(Default, Deref, DerefMut)] pub struct AccessKitAdapters(pub EntityHashMap); +impl AccessKitAdapters { + /// Creates a new empty `AccessKitAdapters`. + pub const fn new() -> Self { + Self(EntityHashMap::new()) + } +} + /// Maps window entities to their respective [`ActionRequest`]s. #[derive(Resource, Default, Deref, DerefMut)] pub struct WinitActionRequestHandlers(pub EntityHashMap>>); @@ -66,8 +81,7 @@ impl AccessKitState { fn build_initial_tree(&mut self) -> TreeUpdate { let root = self.build_root(); let accesskit_window_id = NodeId(self.entity.to_bits()); - let mut tree = Tree::new(accesskit_window_id); - tree.app_name = Some(self.name.clone()); + let tree = Tree::new(accesskit_window_id); self.requested.set(true); TreeUpdate { @@ -116,6 +130,7 @@ impl DeactivationHandler for WinitDeactivationHandler { /// Prepares accessibility for a winit window. pub(crate) fn prepare_accessibility_for_window( + event_loop: &ActiveEventLoop, winit_window: &winit::window::Window, entity: Entity, name: String, @@ -131,6 +146,7 @@ pub(crate) fn prepare_accessibility_for_window( let deactivation_handler = WinitDeactivationHandler; let adapter = Adapter::with_direct_handlers( + event_loop, winit_window, activation_handler, action_handler, @@ -142,14 +158,16 @@ pub(crate) fn prepare_accessibility_for_window( } fn window_closed( - mut adapters: NonSendMut, mut handlers: ResMut, mut events: EventReader, + _non_send_marker: NonSendMarker, ) { - for WindowClosed { window, .. } in events.read() { - adapters.remove(window); - handlers.remove(window); - } + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + for WindowClosed { window, .. } in events.read() { + adapters.remove(window); + handlers.remove(window); + } + }); } fn poll_receivers( @@ -172,7 +190,6 @@ fn should_update_accessibility_nodes( } fn update_accessibility_nodes( - mut adapters: NonSendMut, focus: Option>, primary_window: Query<(Entity, &Window), With>, nodes: Query<( @@ -182,35 +199,38 @@ fn update_accessibility_nodes( Option<&ChildOf>, )>, node_entities: Query>, + _non_send_marker: NonSendMarker, ) { - let Ok((primary_window_id, primary_window)) = primary_window.single() else { - return; - }; - let Some(adapter) = adapters.get_mut(&primary_window_id) else { - return; - }; - let Some(focus) = focus else { - return; - }; - if focus.is_changed() || !nodes.is_empty() { - // Don't panic if the focused entity does not currently exist - // It's probably waiting to be spawned - if let Some(focused_entity) = focus.0 { - if !node_entities.contains(focused_entity) { - return; + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + let Ok((primary_window_id, primary_window)) = primary_window.single() else { + return; + }; + let Some(adapter) = adapters.get_mut(&primary_window_id) else { + return; + }; + let Some(focus) = focus else { + return; + }; + if focus.is_changed() || !nodes.is_empty() { + // Don't panic if the focused entity does not currently exist + // It's probably waiting to be spawned + if let Some(focused_entity) = focus.0 { + if !node_entities.contains(focused_entity) { + return; + } } - } - adapter.update_if_active(|| { - update_adapter( - nodes, - node_entities, - primary_window, - primary_window_id, - focus, - ) - }); - } + adapter.update_if_active(|| { + update_adapter( + nodes, + node_entities, + primary_window, + primary_window_id, + focus, + ) + }); + } + }); } fn update_adapter( @@ -258,7 +278,7 @@ fn queue_node_for_update( window_children: &mut Vec, ) { let should_push = if let Some(child_of) = child_of { - !node_entities.contains(child_of.parent) + !node_entities.contains(child_of.parent()) } else { true }; @@ -288,8 +308,7 @@ pub struct AccessKitPlugin; impl Plugin for AccessKitPlugin { fn build(&self, app: &mut App) { - app.init_non_send_resource::() - .init_resource::() + app.init_resource::() .add_event::() .add_systems( PostUpdate, @@ -300,7 +319,7 @@ impl Plugin for AccessKitPlugin { .before(poll_receivers) .before(update_accessibility_nodes), ) - .in_set(AccessibilitySystem::Update), + .in_set(AccessibilitySystems::Update), ); } } diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index ba41c62534..3de27162a4 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -10,6 +10,9 @@ use bevy_window::SystemCursorIcon; use bevy_window::{EnabledButtons, WindowLevel, WindowTheme}; use winit::keyboard::{Key, NamedKey, NativeKey}; +#[cfg(target_os = "ios")] +use bevy_window::ScreenEdge; + pub fn convert_keyboard_input( keyboard_input: &winit::event::KeyEvent, window: Entity, @@ -718,3 +721,16 @@ pub fn convert_resize_direction(resize_direction: CompassOctant) -> winit::windo CompassOctant::SouthEast => winit::window::ResizeDirection::SouthEast, } } + +#[cfg(target_os = "ios")] +/// Converts a [`bevy_window::ScreenEdge`] to a [`winit::platform::ios::ScreenEdge`]. +pub(crate) fn convert_screen_edge(edge: ScreenEdge) -> winit::platform::ios::ScreenEdge { + match edge { + ScreenEdge::None => winit::platform::ios::ScreenEdge::NONE, + ScreenEdge::Top => winit::platform::ios::ScreenEdge::TOP, + ScreenEdge::Bottom => winit::platform::ios::ScreenEdge::BOTTOM, + ScreenEdge::Left => winit::platform::ios::ScreenEdge::LEFT, + ScreenEdge::Right => winit::platform::ios::ScreenEdge::RIGHT, + ScreenEdge::All => winit::platform::ios::ScreenEdge::ALL, + } +} diff --git a/crates/bevy_winit/src/cursor.rs b/crates/bevy_winit/src/cursor.rs index de07581141..bdca3f8585 100644 --- a/crates/bevy_winit/src/cursor.rs +++ b/crates/bevy_winit/src/cursor.rs @@ -30,7 +30,7 @@ use bevy_ecs::{ }; #[cfg(feature = "custom_cursor")] use bevy_image::{Image, TextureAtlasLayout}; -use bevy_platform_support::collections::HashSet; +use bevy_platform::collections::HashSet; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_window::{SystemCursorIcon, Window}; #[cfg(feature = "custom_cursor")] @@ -39,6 +39,13 @@ use tracing::warn; #[cfg(feature = "custom_cursor")] pub use crate::custom_cursor::{CustomCursor, CustomCursorImage}; +#[cfg(all( + feature = "custom_cursor", + target_family = "wasm", + target_os = "unknown" +))] +pub use crate::custom_cursor::CustomCursorUrl; + pub(crate) struct CursorPlugin; impl Plugin for CursorPlugin { diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index db74825f0f..d7c880a9b9 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -18,6 +18,7 @@ use bevy_derive::Deref; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_window::{RawHandleWrapperHolder, WindowEvent}; +use core::cell::RefCell; use core::marker::PhantomData; use winit::{event_loop::EventLoop, window::WindowId}; @@ -37,7 +38,7 @@ pub use winit_config::*; pub use winit_windows::*; use crate::{ - accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers}, + accessibility::{AccessKitPlugin, WinitActionRequestHandlers}, state::winit_runner, winit_monitors::WinitMonitors, }; @@ -53,6 +54,10 @@ mod winit_config; mod winit_monitors; mod winit_windows; +thread_local! { + static WINIT_WINDOWS: RefCell = const { RefCell::new(WinitWindows::new()) }; +} + /// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input /// events. /// @@ -124,9 +129,9 @@ impl Plugin for WinitPlugin { .build() .expect("Failed to build event loop"); - app.init_non_send_resource::() - .init_resource::() + app.init_resource::() .init_resource::() + .insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle())) .add_event::() .set_runner(|app| winit_runner(app, event_loop)) .add_systems( @@ -176,6 +181,15 @@ pub struct RawWinitWindowEvent { #[derive(Resource, Deref)] pub struct EventLoopProxyWrapper(EventLoopProxy); +/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`] +/// +/// The `DisplayHandleWrapper` can be used to build integrations that rely on direct +/// access to the display handle +/// +/// Use `Res` to receive this resource. +#[derive(Resource, Deref)] +pub struct DisplayHandleWrapper(pub winit::event_loop::OwnedDisplayHandle); + trait AppSendEvent { fn send(&mut self, event: impl Into); } @@ -200,8 +214,6 @@ pub type CreateWindowParams<'w, 's, F = ()> = ( F, >, EventWriter<'w, WindowCreated>, - NonSendMut<'w, WinitWindows>, - NonSendMut<'w, AccessKitAdapters>, ResMut<'w, WinitActionRequestHandlers>, Res<'w, AccessibilityRequested>, Res<'w, WinitMonitors>, diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index f1926275ab..083341fd2b 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -3,7 +3,7 @@ use bevy_app::{App, AppExit, PluginsState}; #[cfg(feature = "custom_cursor")] use bevy_asset::AssetId; use bevy_ecs::{ - change_detection::{DetectChanges, NonSendMut, Res}, + change_detection::{DetectChanges, Res}, entity::Entity, event::{EventCursor, EventWriter}, prelude::*, @@ -16,13 +16,15 @@ use bevy_input::{ gestures::*, mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, }; -use bevy_log::{error, trace, warn}; +#[cfg(any(not(target_arch = "wasm32"), feature = "custom_cursor"))] +use bevy_log::error; +use bevy_log::{trace, warn}; #[cfg(feature = "custom_cursor")] use bevy_math::URect; use bevy_math::{ivec2, DVec2, Vec2}; #[cfg(feature = "custom_cursor")] -use bevy_platform_support::collections::HashMap; -use bevy_platform_support::time::Instant; +use bevy_platform::collections::HashMap; +use bevy_platform::time::Instant; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; use core::marker::PhantomData; @@ -47,11 +49,11 @@ use bevy_window::{ use bevy_window::{PrimaryWindow, RawHandleWrapper}; use crate::{ - accessibility::AccessKitAdapters, + accessibility::ACCESS_KIT_ADAPTERS, converters, create_windows, system::{create_monitors, CachedWindow, WinitWindowPressedKeys}, AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper, - RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, + RawWinitWindowEvent, UpdateMode, WinitSettings, WINIT_WINDOWS, }; /// Persistent state that is used to run the [`App`] according to the current @@ -92,7 +94,6 @@ struct WinitAppRunnerState { EventWriter<'static, WindowResized>, EventWriter<'static, WindowBackendScaleFactorChanged>, EventWriter<'static, WindowScaleFactorChanged>, - NonSend<'static, WinitWindows>, Query< 'static, 'static, @@ -102,22 +103,20 @@ struct WinitAppRunnerState { &'static mut WinitWindowPressedKeys, ), >, - NonSendMut<'static, AccessKitAdapters>, )>, } impl WinitAppRunnerState { fn new(mut app: App) -> Self { + app.add_event::(); #[cfg(feature = "custom_cursor")] - app.add_event::().init_resource::(); + app.init_resource::(); let event_writer_system_state: SystemState<( EventWriter, EventWriter, EventWriter, - NonSend, Query<(&mut Window, &mut CachedWindow, &mut WinitWindowPressedKeys)>, - NonSendMut, )> = SystemState::new(app.world_mut()); Self { @@ -253,220 +252,236 @@ impl ApplicationHandler for WinitAppRunnerState { ) { self.window_event_received = true; - let ( - mut window_resized, - mut window_backend_scale_factor_changed, - mut window_scale_factor_changed, - winit_windows, - mut windows, - mut access_kit_adapters, - ) = self.event_writer_system_state.get_mut(self.app.world_mut()); + #[cfg_attr( + not(target_os = "windows"), + expect(unused_mut, reason = "only needs to be mut on windows for now") + )] + let mut manual_run_redraw_requested = false; - let Some(window) = winit_windows.get_window_entity(window_id) else { - warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}"); - return; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|access_kit_adapters| { + let ( + mut window_resized, + mut window_backend_scale_factor_changed, + mut window_scale_factor_changed, + mut windows, + ) = self.event_writer_system_state.get_mut(self.app.world_mut()); - let Ok((mut win, _, mut pressed_keys)) = windows.get_mut(window) else { - warn!("Window {window:?} is missing `Window` component, skipping event {event:?}"); - return; - }; + let Some(window) = winit_windows.get_window_entity(window_id) else { + warn!("Skipped event {event:?} for unknown winit Window Id {window_id:?}"); + return; + }; - // Store a copy of the event to send to an EventWriter later. - self.raw_winit_events.push(RawWinitWindowEvent { - window_id, - event: event.clone(), - }); + let Ok((mut win, _, mut pressed_keys)) = windows.get_mut(window) else { + warn!( + "Window {window:?} is missing `Window` component, skipping event {event:?}" + ); + return; + }; - // Allow AccessKit to respond to `WindowEvent`s before they reach - // the engine. - if let Some(adapter) = access_kit_adapters.get_mut(&window) { - if let Some(winit_window) = winit_windows.get_window(window) { - adapter.process_event(winit_window, &event); - } - } - - match event { - WindowEvent::Resized(size) => { - react_to_resize(window, &mut win, size, &mut window_resized); - } - WindowEvent::ScaleFactorChanged { scale_factor, .. } => { - react_to_scale_factor_change( - window, - &mut win, - scale_factor, - &mut window_backend_scale_factor_changed, - &mut window_scale_factor_changed, - ); - } - WindowEvent::CloseRequested => self - .bevy_window_events - .send(WindowCloseRequested { window }), - WindowEvent::KeyboardInput { - ref event, - // On some platforms, winit sends "synthetic" key press events when the window - // gains or loses focus. These are not implemented on every platform, so we ignore - // winit's synthetic key pressed and implement the same mechanism ourselves. - // (See the `WinitWindowPressedKeys` component) - is_synthetic: false, - .. - } => { - let keyboard_input = converters::convert_keyboard_input(event, window); - if event.state.is_pressed() { - pressed_keys - .0 - .insert(keyboard_input.key_code, keyboard_input.logical_key.clone()); - } else { - pressed_keys.0.remove(&keyboard_input.key_code); - } - self.bevy_window_events.send(keyboard_input); - } - WindowEvent::CursorMoved { position, .. } => { - let physical_position = DVec2::new(position.x, position.y); - - let last_position = win.physical_cursor_position(); - let delta = last_position.map(|last_pos| { - (physical_position.as_vec2() - last_pos) / win.resolution.scale_factor() + // Store a copy of the event to send to an EventWriter later. + self.raw_winit_events.push(RawWinitWindowEvent { + window_id, + event: event.clone(), }); - win.set_physical_cursor_position(Some(physical_position)); - let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2(); - self.bevy_window_events.send(CursorMoved { - window, - position, - delta, - }); - } - WindowEvent::CursorEntered { .. } => { - self.bevy_window_events.send(CursorEntered { window }); - } - WindowEvent::CursorLeft { .. } => { - win.set_physical_cursor_position(None); - self.bevy_window_events.send(CursorLeft { window }); - } - WindowEvent::MouseInput { state, button, .. } => { - self.bevy_window_events.send(MouseButtonInput { - button: converters::convert_mouse_button(button), - state: converters::convert_element_state(state), - window, - }); - } - WindowEvent::PinchGesture { delta, .. } => { - self.bevy_window_events.send(PinchGesture(delta as f32)); - } - WindowEvent::RotationGesture { delta, .. } => { - self.bevy_window_events.send(RotationGesture(delta)); - } - WindowEvent::DoubleTapGesture { .. } => { - self.bevy_window_events.send(DoubleTapGesture); - } - WindowEvent::PanGesture { delta, .. } => { - self.bevy_window_events.send(PanGesture(Vec2 { - x: delta.x, - y: delta.y, - })); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - event::MouseScrollDelta::LineDelta(x, y) => { - self.bevy_window_events.send(MouseWheel { - unit: MouseScrollUnit::Line, - x, - y, - window, - }); - } - event::MouseScrollDelta::PixelDelta(p) => { - self.bevy_window_events.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: p.x as f32, - y: p.y as f32, - window, - }); - } - }, - WindowEvent::Touch(touch) => { - let location = touch - .location - .to_logical(win.resolution.scale_factor() as f64); - self.bevy_window_events - .send(converters::convert_touch_input(touch, location, window)); - } - WindowEvent::Focused(focused) => { - win.focused = focused; - self.bevy_window_events - .send(WindowFocused { window, focused }); - } - WindowEvent::Occluded(occluded) => { - self.bevy_window_events - .send(WindowOccluded { window, occluded }); - } - WindowEvent::DroppedFile(path_buf) => { - self.bevy_window_events - .send(FileDragAndDrop::DroppedFile { window, path_buf }); - } - WindowEvent::HoveredFile(path_buf) => { - self.bevy_window_events - .send(FileDragAndDrop::HoveredFile { window, path_buf }); - } - WindowEvent::HoveredFileCancelled => { - self.bevy_window_events - .send(FileDragAndDrop::HoveredFileCanceled { window }); - } - WindowEvent::Moved(position) => { - let position = ivec2(position.x, position.y); - win.position.set(position); - self.bevy_window_events - .send(WindowMoved { window, position }); - } - WindowEvent::Ime(event) => match event { - event::Ime::Preedit(value, cursor) => { - self.bevy_window_events.send(Ime::Preedit { - window, - value, - cursor, - }); - } - event::Ime::Commit(value) => { - self.bevy_window_events.send(Ime::Commit { window, value }); - } - event::Ime::Enabled => { - self.bevy_window_events.send(Ime::Enabled { window }); - } - event::Ime::Disabled => { - self.bevy_window_events.send(Ime::Disabled { window }); - } - }, - WindowEvent::ThemeChanged(theme) => { - self.bevy_window_events.send(WindowThemeChanged { - window, - theme: converters::convert_winit_theme(theme), - }); - } - WindowEvent::Destroyed => { - self.bevy_window_events.send(WindowDestroyed { window }); - } - WindowEvent::RedrawRequested => { - self.ran_update_since_last_redraw = false; - - // https://github.com/bevyengine/bevy/issues/17488 - #[cfg(target_os = "windows")] - { - // Have the startup behavior run in about_to_wait, which prevents issues with - // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 - if self.startup_forced_updates == 0 { - self.redraw_requested = true; - self.redraw_requested(_event_loop); + // Allow AccessKit to respond to `WindowEvent`s before they reach + // the engine. + if let Some(adapter) = access_kit_adapters.get_mut(&window) { + if let Some(winit_window) = winit_windows.get_window(window) { + adapter.process_event(winit_window, &event); } } - } - _ => {} - } - let mut windows = self.world_mut().query::<(&mut Window, &mut CachedWindow)>(); - if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window) { - if window_component.is_changed() { - cache.window = window_component.clone(); - } + match event { + WindowEvent::Resized(size) => { + react_to_resize(window, &mut win, size, &mut window_resized); + } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + react_to_scale_factor_change( + window, + &mut win, + scale_factor, + &mut window_backend_scale_factor_changed, + &mut window_scale_factor_changed, + ); + } + WindowEvent::CloseRequested => self + .bevy_window_events + .send(WindowCloseRequested { window }), + WindowEvent::KeyboardInput { + ref event, + // On some platforms, winit sends "synthetic" key press events when the window + // gains or loses focus. These are not implemented on every platform, so we ignore + // winit's synthetic key pressed and implement the same mechanism ourselves. + // (See the `WinitWindowPressedKeys` component) + is_synthetic: false, + .. + } => { + let keyboard_input = converters::convert_keyboard_input(event, window); + if event.state.is_pressed() { + pressed_keys.0.insert( + keyboard_input.key_code, + keyboard_input.logical_key.clone(), + ); + } else { + pressed_keys.0.remove(&keyboard_input.key_code); + } + self.bevy_window_events.send(keyboard_input); + } + WindowEvent::CursorMoved { position, .. } => { + let physical_position = DVec2::new(position.x, position.y); + + let last_position = win.physical_cursor_position(); + let delta = last_position.map(|last_pos| { + (physical_position.as_vec2() - last_pos) / win.resolution.scale_factor() + }); + + win.set_physical_cursor_position(Some(physical_position)); + let position = + (physical_position / win.resolution.scale_factor() as f64).as_vec2(); + self.bevy_window_events.send(CursorMoved { + window, + position, + delta, + }); + } + WindowEvent::CursorEntered { .. } => { + self.bevy_window_events.send(CursorEntered { window }); + } + WindowEvent::CursorLeft { .. } => { + win.set_physical_cursor_position(None); + self.bevy_window_events.send(CursorLeft { window }); + } + WindowEvent::MouseInput { state, button, .. } => { + self.bevy_window_events.send(MouseButtonInput { + button: converters::convert_mouse_button(button), + state: converters::convert_element_state(state), + window, + }); + } + WindowEvent::PinchGesture { delta, .. } => { + self.bevy_window_events.send(PinchGesture(delta as f32)); + } + WindowEvent::RotationGesture { delta, .. } => { + self.bevy_window_events.send(RotationGesture(delta)); + } + WindowEvent::DoubleTapGesture { .. } => { + self.bevy_window_events.send(DoubleTapGesture); + } + WindowEvent::PanGesture { delta, .. } => { + self.bevy_window_events.send(PanGesture(Vec2 { + x: delta.x, + y: delta.y, + })); + } + WindowEvent::MouseWheel { delta, .. } => match delta { + event::MouseScrollDelta::LineDelta(x, y) => { + self.bevy_window_events.send(MouseWheel { + unit: MouseScrollUnit::Line, + x, + y, + window, + }); + } + event::MouseScrollDelta::PixelDelta(p) => { + self.bevy_window_events.send(MouseWheel { + unit: MouseScrollUnit::Pixel, + x: p.x as f32, + y: p.y as f32, + window, + }); + } + }, + WindowEvent::Touch(touch) => { + let location = touch + .location + .to_logical(win.resolution.scale_factor() as f64); + self.bevy_window_events + .send(converters::convert_touch_input(touch, location, window)); + } + WindowEvent::Focused(focused) => { + win.focused = focused; + self.bevy_window_events + .send(WindowFocused { window, focused }); + } + WindowEvent::Occluded(occluded) => { + self.bevy_window_events + .send(WindowOccluded { window, occluded }); + } + WindowEvent::DroppedFile(path_buf) => { + self.bevy_window_events + .send(FileDragAndDrop::DroppedFile { window, path_buf }); + } + WindowEvent::HoveredFile(path_buf) => { + self.bevy_window_events + .send(FileDragAndDrop::HoveredFile { window, path_buf }); + } + WindowEvent::HoveredFileCancelled => { + self.bevy_window_events + .send(FileDragAndDrop::HoveredFileCanceled { window }); + } + WindowEvent::Moved(position) => { + let position = ivec2(position.x, position.y); + win.position.set(position); + self.bevy_window_events + .send(WindowMoved { window, position }); + } + WindowEvent::Ime(event) => match event { + event::Ime::Preedit(value, cursor) => { + self.bevy_window_events.send(Ime::Preedit { + window, + value, + cursor, + }); + } + event::Ime::Commit(value) => { + self.bevy_window_events.send(Ime::Commit { window, value }); + } + event::Ime::Enabled => { + self.bevy_window_events.send(Ime::Enabled { window }); + } + event::Ime::Disabled => { + self.bevy_window_events.send(Ime::Disabled { window }); + } + }, + WindowEvent::ThemeChanged(theme) => { + self.bevy_window_events.send(WindowThemeChanged { + window, + theme: converters::convert_winit_theme(theme), + }); + } + WindowEvent::Destroyed => { + self.bevy_window_events.send(WindowDestroyed { window }); + } + WindowEvent::RedrawRequested => { + self.ran_update_since_last_redraw = false; + + // https://github.com/bevyengine/bevy/issues/17488 + #[cfg(target_os = "windows")] + { + // Have the startup behavior run in about_to_wait, which prevents issues with + // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 + if self.startup_forced_updates == 0 { + manual_run_redraw_requested = true; + } + } + } + _ => {} + } + + let mut windows = self.world_mut().query::<(&mut Window, &mut CachedWindow)>(); + if let Ok((window_component, mut cache)) = windows.get_mut(self.world_mut(), window) + { + if window_component.is_changed() { + cache.window = window_component.clone(); + } + } + }); + }); + + if manual_run_redraw_requested { + self.redraw_requested(_event_loop); } } @@ -505,16 +520,20 @@ impl ApplicationHandler for WinitAppRunnerState { // invisible window creation. https://github.com/bevyengine/bevy/issues/18027 #[cfg(target_os = "windows")] { - let winit_windows = self.world().non_send_resource::(); - let headless = winit_windows.windows.is_empty(); - let exiting = self.app_exit.is_some(); - let all_invisible = winit_windows - .windows - .iter() - .all(|(_, w)| !w.is_visible().unwrap_or(false)); - if !exiting && (self.startup_forced_updates > 0 || headless || all_invisible) { - self.redraw_requested(event_loop); - } + WINIT_WINDOWS.with_borrow(|winit_windows| { + let headless = winit_windows.windows.is_empty(); + let exiting = self.app_exit.is_some(); + let reactive = matches!(self.update_mode, UpdateMode::Reactive { .. }); + let all_invisible = winit_windows + .windows + .iter() + .all(|(_, w)| !w.is_visible().unwrap_or(false)); + if !exiting + && (self.startup_forced_updates > 0 || headless || all_invisible || reactive) + { + self.redraw_requested(event_loop); + } + }); } } @@ -587,35 +606,33 @@ impl WinitAppRunnerState { // Get windows that are cached but without raw handles. Those window were already created, but got their // handle wrapper removed when the app was suspended. let mut query = self.world_mut() - .query_filtered::<(Entity, &Window), (With, Without)>(); + .query_filtered::<(Entity, &Window), (With, Without)>(); if let Ok((entity, window)) = query.single(&self.world()) { let window = window.clone(); - let mut create_window = - SystemState::::from_world(self.world_mut()); + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + let mut create_window = + SystemState::::from_world(self.world_mut()); - let ( - .., - mut winit_windows, - mut adapters, - mut handlers, - accessibility_requested, - monitors, - ) = create_window.get_mut(self.world_mut()); + let (.., mut handlers, accessibility_requested, monitors) = + create_window.get_mut(self.world_mut()); - let winit_window = winit_windows.create_window( - event_loop, - entity, - &window, - &mut adapters, - &mut handlers, - &accessibility_requested, - &monitors, - ); + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + adapters, + &mut handlers, + &accessibility_requested, + &monitors, + ); - let wrapper = RawHandleWrapper::new(winit_window).unwrap(); + let wrapper = RawHandleWrapper::new(winit_window).unwrap(); - self.world_mut().entity_mut(entity).insert(wrapper); + self.world_mut().entity_mut(entity).insert(wrapper); + }); + }); } } } @@ -680,9 +697,10 @@ impl WinitAppRunnerState { all(target_os = "linux", any(feature = "x11", feature = "wayland")) )))] { - let winit_windows = self.world().non_send_resource::(); - let visible = winit_windows.windows.iter().any(|(_, w)| { - w.is_visible().unwrap_or(false) + let visible = WINIT_WINDOWS.with_borrow(|winit_windows| { + winit_windows.windows.iter().any(|(_, w)| { + w.is_visible().unwrap_or(false) + }) }); event_loop.set_control_flow(if visible { @@ -712,10 +730,11 @@ impl WinitAppRunnerState { } if self.redraw_requested && self.lifecycle != AppLifecycle::Suspended { - let winit_windows = self.world().non_send_resource::(); - for window in winit_windows.windows.values() { - window.request_redraw(); - } + WINIT_WINDOWS.with_borrow(|winit_windows| { + for window in winit_windows.windows.values() { + window.request_redraw(); + } + }); self.redraw_requested = false; } @@ -867,48 +886,47 @@ impl WinitAppRunnerState { fn update_cursors(&mut self, #[cfg(feature = "custom_cursor")] event_loop: &ActiveEventLoop) { #[cfg(feature = "custom_cursor")] let mut windows_state: SystemState<( - NonSendMut, ResMut, Query<(Entity, &mut PendingCursor), Changed>, )> = SystemState::new(self.world_mut()); #[cfg(feature = "custom_cursor")] - let (winit_windows, mut cursor_cache, mut windows) = - windows_state.get_mut(self.world_mut()); + let (mut cursor_cache, mut windows) = windows_state.get_mut(self.world_mut()); #[cfg(not(feature = "custom_cursor"))] let mut windows_state: SystemState<( - NonSendMut, Query<(Entity, &mut PendingCursor), Changed>, )> = SystemState::new(self.world_mut()); #[cfg(not(feature = "custom_cursor"))] - let (winit_windows, mut windows) = windows_state.get_mut(self.world_mut()); + let (mut windows,) = windows_state.get_mut(self.world_mut()); - for (entity, mut pending_cursor) in windows.iter_mut() { - let Some(winit_window) = winit_windows.get_window(entity) else { - continue; - }; - let Some(pending_cursor) = pending_cursor.0.take() else { - continue; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + for (entity, mut pending_cursor) in windows.iter_mut() { + let Some(winit_window) = winit_windows.get_window(entity) else { + continue; + }; + let Some(pending_cursor) = pending_cursor.0.take() else { + continue; + }; - let final_cursor: winit::window::Cursor = match pending_cursor { - #[cfg(feature = "custom_cursor")] - CursorSource::CustomCached(cache_key) => { - let Some(cached_cursor) = cursor_cache.0.get(&cache_key) else { - error!("Cursor should have been cached, but was not found"); - continue; - }; - cached_cursor.clone().into() - } - #[cfg(feature = "custom_cursor")] - CursorSource::Custom((cache_key, cursor)) => { - let custom_cursor = event_loop.create_custom_cursor(cursor); - cursor_cache.0.insert(cache_key, custom_cursor.clone()); - custom_cursor.into() - } - CursorSource::System(system_cursor) => system_cursor.into(), - }; - winit_window.set_cursor(final_cursor); - } + let final_cursor: winit::window::Cursor = match pending_cursor { + #[cfg(feature = "custom_cursor")] + CursorSource::CustomCached(cache_key) => { + let Some(cached_cursor) = cursor_cache.0.get(&cache_key) else { + error!("Cursor should have been cached, but was not found"); + continue; + }; + cached_cursor.clone().into() + } + #[cfg(feature = "custom_cursor")] + CursorSource::Custom((cache_key, cursor)) => { + let custom_cursor = event_loop.create_custom_cursor(cursor); + cursor_cache.0.insert(cache_key, custom_cursor.clone()); + custom_cursor.into() + } + CursorSource::System(system_cursor) => system_cursor.into(), + }; + winit_window.set_cursor(final_cursor); + } + }); } } diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index f4ed1a59a3..97483c7358 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ prelude::{Changed, Component}, query::QueryFilter, removal_detection::RemovedComponents, - system::{Local, NonSendMut, Query, SystemParamItem}, + system::{Local, NonSendMarker, Query, SystemParamItem}, }; use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput}; use bevy_window::{ @@ -30,6 +30,7 @@ use winit::platform::ios::WindowExtIOS; use winit::platform::web::WindowExtWebSys; use crate::{ + accessibility::ACCESS_KIT_ADAPTERS, converters::{ convert_enabled_buttons, convert_resize_direction, convert_window_level, convert_window_theme, convert_winit_theme, @@ -37,7 +38,7 @@ use crate::{ get_selected_videomode, select_monitor, state::react_to_resize, winit_monitors::WinitMonitors, - CreateMonitorParams, CreateWindowParams, WinitWindows, + CreateMonitorParams, CreateWindowParams, WINIT_WINDOWS, }; /// Creates new windows on the [`winit`] backend for each entity with a newly-added @@ -51,78 +52,80 @@ pub fn create_windows( mut commands, mut created_windows, mut window_created_events, - mut winit_windows, - mut adapters, mut handlers, accessibility_requested, monitors, ): SystemParamItem>, ) { - for (entity, mut window, handle_holder) in &mut created_windows { - if winit_windows.get_window(entity).is_some() { - continue; - } + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| { + for (entity, mut window, handle_holder) in &mut created_windows { + if winit_windows.get_window(entity).is_some() { + continue; + } - info!("Creating new window {} ({})", window.title.as_str(), entity); + info!("Creating new window {} ({})", window.title.as_str(), entity); - let winit_window = winit_windows.create_window( - event_loop, - entity, - &window, - &mut adapters, - &mut handlers, - &accessibility_requested, - &monitors, - ); + let winit_window = winit_windows.create_window( + event_loop, + entity, + &window, + adapters, + &mut handlers, + &accessibility_requested, + &monitors, + ); - if let Some(theme) = winit_window.theme() { - window.window_theme = Some(convert_winit_theme(theme)); - } + if let Some(theme) = winit_window.theme() { + window.window_theme = Some(convert_winit_theme(theme)); + } - window - .resolution - .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32); + window + .resolution + .set_scale_factor_and_apply_to_physical_size(winit_window.scale_factor() as f32); - commands.entity(entity).insert(( - CachedWindow { - window: window.clone(), - }, - WinitWindowPressedKeys::default(), - )); + commands.entity(entity).insert(( + CachedWindow { + window: window.clone(), + }, + WinitWindowPressedKeys::default(), + )); - if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) { - commands.entity(entity).insert(handle_wrapper.clone()); - if let Some(handle_holder) = handle_holder { - *handle_holder.0.lock().unwrap() = Some(handle_wrapper); + if let Ok(handle_wrapper) = RawHandleWrapper::new(winit_window) { + commands.entity(entity).insert(handle_wrapper.clone()); + if let Some(handle_holder) = handle_holder { + *handle_holder.0.lock().unwrap() = Some(handle_wrapper); + } + } + + #[cfg(target_arch = "wasm32")] + { + if window.fit_canvas_to_parent { + let canvas = winit_window + .canvas() + .expect("window.canvas() can only be called in main thread."); + let style = canvas.style(); + style.set_property("width", "100%").unwrap(); + style.set_property("height", "100%").unwrap(); + } + } + + #[cfg(target_os = "ios")] + { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + if let Some((min, max)) = window.recognize_pan_gesture { + winit_window.recognize_pan_gesture(true, min, max); + } else { + winit_window.recognize_pan_gesture(false, 0, 0); + } + } + + window_created_events.write(WindowCreated { window: entity }); } - } - - #[cfg(target_arch = "wasm32")] - { - if window.fit_canvas_to_parent { - let canvas = winit_window - .canvas() - .expect("window.canvas() can only be called in main thread."); - let style = canvas.style(); - style.set_property("width", "100%").unwrap(); - style.set_property("height", "100%").unwrap(); - } - } - - #[cfg(target_os = "ios")] - { - winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); - winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); - winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); - if let Some((min, max)) = window.recognize_pan_gesture { - winit_window.recognize_pan_gesture(true, min, max); - } else { - winit_window.recognize_pan_gesture(false, 0, 0); - } - } - - window_created_events.write(WindowCreated { window: entity }); - } + }); + }); } /// Check whether keyboard focus was lost. This is different from window @@ -239,9 +242,9 @@ pub(crate) fn despawn_windows( window_entities: Query>, mut closing_events: EventWriter, mut closed_events: EventWriter, - mut winit_windows: NonSendMut, mut windows_to_drop: Local>>, mut exit_events: EventReader, + _non_send_marker: NonSendMarker, ) { // Drop all the windows that are waiting to be closed windows_to_drop.clear(); @@ -254,13 +257,15 @@ pub(crate) fn despawn_windows( // rather than having the component added // and removed in the same frame. if !window_entities.contains(window) { - if let Some(window) = winit_windows.remove_window(window) { - // Keeping WindowWrapper that are dropped for one frame - // Otherwise the last `Arc` of the window could be in the rendering thread, and dropped there - // This would hang on macOS - // Keeping the wrapper and dropping it next frame in this system ensure its dropped in the main thread - windows_to_drop.push(window); - } + WINIT_WINDOWS.with_borrow_mut(|winit_windows| { + if let Some(window) = winit_windows.remove_window(window) { + // Keeping WindowWrapper that are dropped for one frame + // Otherwise the last `Arc` of the window could be in the rendering thread, and dropped there + // This would hang on macOS + // Keeping the wrapper and dropping it next frame in this system ensure its dropped in the main thread + windows_to_drop.push(window); + } + }); closed_events.write(WindowClosed { window }); } } @@ -291,286 +296,298 @@ pub struct CachedWindow { /// - [`Window::focused`] cannot be manually changed to `false` after the window is created. pub(crate) fn changed_windows( mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed>, - winit_windows: NonSendMut, monitors: Res, mut window_resized: EventWriter, + _non_send_marker: NonSendMarker, ) { - for (entity, mut window, mut cache) in &mut changed_windows { - let Some(winit_window) = winit_windows.get_window(entity) else { - continue; - }; + WINIT_WINDOWS.with_borrow(|winit_windows| { + for (entity, mut window, mut cache) in &mut changed_windows { + let Some(winit_window) = winit_windows.get_window(entity) else { + continue; + }; - if window.title != cache.window.title { - winit_window.set_title(window.title.as_str()); - } + if window.title != cache.window.title { + winit_window.set_title(window.title.as_str()); + } - if window.mode != cache.window.mode { - let new_mode = match window.mode { - WindowMode::BorderlessFullscreen(monitor_selection) => { - Some(Some(winit::window::Fullscreen::Borderless(select_monitor( - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - &monitor_selection, - )))) - } - WindowMode::Fullscreen(monitor_selection, video_mode_selection) => { - let monitor = &select_monitor( - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - &monitor_selection, - ) - .unwrap_or_else(|| { - panic!("Could not find monitor for {:?}", monitor_selection) - }); - - if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection) - { - Some(Some(winit::window::Fullscreen::Exclusive(video_mode))) - } else { - warn!( - "Could not find valid fullscreen video mode for {:?} {:?}", - monitor_selection, video_mode_selection - ); - None + if window.mode != cache.window.mode { + let new_mode = match window.mode { + WindowMode::BorderlessFullscreen(monitor_selection) => { + Some(Some(winit::window::Fullscreen::Borderless(select_monitor( + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + &monitor_selection, + )))) } - } - WindowMode::Windowed => Some(None), - }; + WindowMode::Fullscreen(monitor_selection, video_mode_selection) => { + let monitor = &select_monitor( + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + &monitor_selection, + ) + .unwrap_or_else(|| { + panic!("Could not find monitor for {:?}", monitor_selection) + }); - if let Some(new_mode) = new_mode { - if winit_window.fullscreen() != new_mode { - winit_window.set_fullscreen(new_mode); - } - } - } - - if window.resolution != cache.window.resolution { - let mut physical_size = PhysicalSize::new( - window.resolution.physical_width(), - window.resolution.physical_height(), - ); - - let cached_physical_size = PhysicalSize::new( - cache.window.physical_width(), - cache.window.physical_height(), - ); - - let base_scale_factor = window.resolution.base_scale_factor(); - - // Note: this may be different from `winit`'s base scale factor if - // `scale_factor_override` is set to Some(f32) - let scale_factor = window.scale_factor(); - let cached_scale_factor = cache.window.scale_factor(); - - // Check and update `winit`'s physical size only if the window is not maximized - if scale_factor != cached_scale_factor && !winit_window.is_maximized() { - let logical_size = - if let Some(cached_factor) = cache.window.resolution.scale_factor_override() { - physical_size.to_logical::(cached_factor as f64) - } else { - physical_size.to_logical::(base_scale_factor as f64) - }; - - // Scale factor changed, updating physical and logical size - if let Some(forced_factor) = window.resolution.scale_factor_override() { - // This window is overriding the OS-suggested DPI, so its physical size - // should be set based on the overriding value. Its logical size already - // incorporates any resize constraints. - physical_size = logical_size.to_physical::(forced_factor as f64); - } else { - physical_size = logical_size.to_physical::(base_scale_factor as f64); - } - } - - if physical_size != cached_physical_size { - if let Some(new_physical_size) = winit_window.request_inner_size(physical_size) { - react_to_resize(entity, &mut window, new_physical_size, &mut window_resized); - } - } - } - - if window.physical_cursor_position() != cache.window.physical_cursor_position() { - if let Some(physical_position) = window.physical_cursor_position() { - let position = PhysicalPosition::new(physical_position.x, physical_position.y); - - if let Err(err) = winit_window.set_cursor_position(position) { - error!("could not set cursor position: {}", err); - } - } - } - - if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode - && crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode) - .is_err() - { - window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode; - } - - if window.cursor_options.visible != cache.window.cursor_options.visible { - winit_window.set_cursor_visible(window.cursor_options.visible); - } - - if window.cursor_options.hit_test != cache.window.cursor_options.hit_test { - if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { - window.cursor_options.hit_test = cache.window.cursor_options.hit_test; - warn!( - "Could not set cursor hit test for window {}: {}", - window.title, err - ); - } - } - - if window.decorations != cache.window.decorations - && window.decorations != winit_window.is_decorated() - { - winit_window.set_decorations(window.decorations); - } - - if window.resizable != cache.window.resizable - && window.resizable != winit_window.is_resizable() - { - winit_window.set_resizable(window.resizable); - } - - if window.enabled_buttons != cache.window.enabled_buttons { - winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); - } - - if window.resize_constraints != cache.window.resize_constraints { - let constraints = window.resize_constraints.check_constraints(); - let min_inner_size = LogicalSize { - width: constraints.min_width, - height: constraints.min_height, - }; - let max_inner_size = LogicalSize { - width: constraints.max_width, - height: constraints.max_height, - }; - - winit_window.set_min_inner_size(Some(min_inner_size)); - if constraints.max_width.is_finite() && constraints.max_height.is_finite() { - winit_window.set_max_inner_size(Some(max_inner_size)); - } - } - - if window.position != cache.window.position { - if let Some(position) = crate::winit_window_position( - &window.position, - &window.resolution, - &monitors, - winit_window.primary_monitor(), - winit_window.current_monitor(), - ) { - let should_set = match winit_window.outer_position() { - Ok(current_position) => current_position != position, - _ => true, + if let Some(video_mode) = get_selected_videomode(monitor, &video_mode_selection) + { + Some(Some(winit::window::Fullscreen::Exclusive(video_mode))) + } else { + warn!( + "Could not find valid fullscreen video mode for {:?} {:?}", + monitor_selection, video_mode_selection + ); + None + } + } + WindowMode::Windowed => Some(None), }; - if should_set { - winit_window.set_outer_position(position); - } - } - } - - if let Some(maximized) = window.internal.take_maximize_request() { - winit_window.set_maximized(maximized); - } - - if let Some(minimized) = window.internal.take_minimize_request() { - winit_window.set_minimized(minimized); - } - - if window.internal.take_move_request() { - if let Err(e) = winit_window.drag_window() { - warn!("Winit returned an error while attempting to drag the window: {e}"); - } - } - - if let Some(resize_direction) = window.internal.take_resize_request() { - if let Err(e) = - winit_window.drag_resize_window(convert_resize_direction(resize_direction)) - { - warn!("Winit returned an error while attempting to drag resize the window: {e}"); - } - } - - if window.focused != cache.window.focused && window.focused { - winit_window.focus_window(); - } - - if window.window_level != cache.window.window_level { - winit_window.set_window_level(convert_window_level(window.window_level)); - } - - // Currently unsupported changes - if window.transparent != cache.window.transparent { - window.transparent = cache.window.transparent; - warn!("Winit does not currently support updating transparency after window creation."); - } - - #[cfg(target_arch = "wasm32")] - if window.canvas != cache.window.canvas { - window.canvas.clone_from(&cache.window.canvas); - warn!( - "Bevy currently doesn't support modifying the window canvas after initialization." - ); - } - - if window.ime_enabled != cache.window.ime_enabled { - winit_window.set_ime_allowed(window.ime_enabled); - } - - if window.ime_position != cache.window.ime_position { - winit_window.set_ime_cursor_area( - LogicalPosition::new(window.ime_position.x, window.ime_position.y), - PhysicalSize::new(10, 10), - ); - } - - if window.window_theme != cache.window.window_theme { - winit_window.set_theme(window.window_theme.map(convert_window_theme)); - } - - if window.visible != cache.window.visible { - winit_window.set_visible(window.visible); - } - - #[cfg(target_os = "ios")] - { - if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { - winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); - } - if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { - winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); - } - if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { - winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); - } - if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { - match ( - window.recognize_pan_gesture, - cache.window.recognize_pan_gesture, - ) { - (Some(_), Some(_)) => { - warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); + if let Some(new_mode) = new_mode { + if winit_window.fullscreen() != new_mode { + winit_window.set_fullscreen(new_mode); } - (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max), - _ => winit_window.recognize_pan_gesture(false, 0, 0), } } - if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden { - winit_window - .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); + if window.resolution != cache.window.resolution { + let mut physical_size = PhysicalSize::new( + window.resolution.physical_width(), + window.resolution.physical_height(), + ); + + let cached_physical_size = PhysicalSize::new( + cache.window.physical_width(), + cache.window.physical_height(), + ); + + let base_scale_factor = window.resolution.base_scale_factor(); + + // Note: this may be different from `winit`'s base scale factor if + // `scale_factor_override` is set to Some(f32) + let scale_factor = window.scale_factor(); + let cached_scale_factor = cache.window.scale_factor(); + + // Check and update `winit`'s physical size only if the window is not maximized + if scale_factor != cached_scale_factor && !winit_window.is_maximized() { + let logical_size = + if let Some(cached_factor) = cache.window.resolution.scale_factor_override() { + physical_size.to_logical::(cached_factor as f64) + } else { + physical_size.to_logical::(base_scale_factor as f64) + }; + + // Scale factor changed, updating physical and logical size + if let Some(forced_factor) = window.resolution.scale_factor_override() { + // This window is overriding the OS-suggested DPI, so its physical size + // should be set based on the overriding value. Its logical size already + // incorporates any resize constraints. + physical_size = logical_size.to_physical::(forced_factor as f64); + } else { + physical_size = logical_size.to_physical::(base_scale_factor as f64); + } + } + + if physical_size != cached_physical_size { + if let Some(new_physical_size) = winit_window.request_inner_size(physical_size) { + react_to_resize(entity, &mut window, new_physical_size, &mut window_resized); + } + } } - if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden { - winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden); + + if window.physical_cursor_position() != cache.window.physical_cursor_position() { + if let Some(physical_position) = window.physical_cursor_position() { + let position = PhysicalPosition::new(physical_position.x, physical_position.y); + + if let Err(err) = winit_window.set_cursor_position(position) { + error!("could not set cursor position: {}", err); + } + } } + + if window.cursor_options.grab_mode != cache.window.cursor_options.grab_mode + && crate::winit_windows::attempt_grab(winit_window, window.cursor_options.grab_mode) + .is_err() + { + window.cursor_options.grab_mode = cache.window.cursor_options.grab_mode; + } + + if window.cursor_options.visible != cache.window.cursor_options.visible { + winit_window.set_cursor_visible(window.cursor_options.visible); + } + + if window.cursor_options.hit_test != cache.window.cursor_options.hit_test { + if let Err(err) = winit_window.set_cursor_hittest(window.cursor_options.hit_test) { + window.cursor_options.hit_test = cache.window.cursor_options.hit_test; + warn!( + "Could not set cursor hit test for window {}: {}", + window.title, err + ); + } + } + + if window.decorations != cache.window.decorations + && window.decorations != winit_window.is_decorated() + { + winit_window.set_decorations(window.decorations); + } + + if window.resizable != cache.window.resizable + && window.resizable != winit_window.is_resizable() + { + winit_window.set_resizable(window.resizable); + } + + if window.enabled_buttons != cache.window.enabled_buttons { + winit_window.set_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)); + } + + if window.resize_constraints != cache.window.resize_constraints { + let constraints = window.resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + winit_window.set_min_inner_size(Some(min_inner_size)); + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window.set_max_inner_size(Some(max_inner_size)); + } + } + + if window.position != cache.window.position { + if let Some(position) = crate::winit_window_position( + &window.position, + &window.resolution, + &monitors, + winit_window.primary_monitor(), + winit_window.current_monitor(), + ) { + let should_set = match winit_window.outer_position() { + Ok(current_position) => current_position != position, + _ => true, + }; + + if should_set { + winit_window.set_outer_position(position); + } + } + } + + if let Some(maximized) = window.internal.take_maximize_request() { + winit_window.set_maximized(maximized); + } + + if let Some(minimized) = window.internal.take_minimize_request() { + winit_window.set_minimized(minimized); + } + + if window.internal.take_move_request() { + if let Err(e) = winit_window.drag_window() { + warn!("Winit returned an error while attempting to drag the window: {e}"); + } + } + + if let Some(resize_direction) = window.internal.take_resize_request() { + if let Err(e) = + winit_window.drag_resize_window(convert_resize_direction(resize_direction)) + { + warn!("Winit returned an error while attempting to drag resize the window: {e}"); + } + } + + if window.focused != cache.window.focused && window.focused { + winit_window.focus_window(); + } + + if window.window_level != cache.window.window_level { + winit_window.set_window_level(convert_window_level(window.window_level)); + } + + // Currently unsupported changes + if window.transparent != cache.window.transparent { + window.transparent = cache.window.transparent; + warn!("Winit does not currently support updating transparency after window creation."); + } + + #[cfg(target_arch = "wasm32")] + if window.canvas != cache.window.canvas { + window.canvas.clone_from(&cache.window.canvas); + warn!( + "Bevy currently doesn't support modifying the window canvas after initialization." + ); + } + + if window.ime_enabled != cache.window.ime_enabled { + winit_window.set_ime_allowed(window.ime_enabled); + } + + if window.ime_position != cache.window.ime_position { + winit_window.set_ime_cursor_area( + LogicalPosition::new(window.ime_position.x, window.ime_position.y), + PhysicalSize::new(10, 10), + ); + } + + if window.window_theme != cache.window.window_theme { + winit_window.set_theme(window.window_theme.map(convert_window_theme)); + } + + if window.visible != cache.window.visible { + winit_window.set_visible(window.visible); + } + + #[cfg(target_os = "ios")] + { + if window.recognize_pinch_gesture != cache.window.recognize_pinch_gesture { + winit_window.recognize_pinch_gesture(window.recognize_pinch_gesture); + } + if window.recognize_rotation_gesture != cache.window.recognize_rotation_gesture { + winit_window.recognize_rotation_gesture(window.recognize_rotation_gesture); + } + if window.recognize_doubletap_gesture != cache.window.recognize_doubletap_gesture { + winit_window.recognize_doubletap_gesture(window.recognize_doubletap_gesture); + } + if window.recognize_pan_gesture != cache.window.recognize_pan_gesture { + match ( + window.recognize_pan_gesture, + cache.window.recognize_pan_gesture, + ) { + (Some(_), Some(_)) => { + warn!("Bevy currently doesn't support modifying PanGesture number of fingers recognition. Please disable it before re-enabling it with the new number of fingers"); + } + (Some((min, max)), _) => winit_window.recognize_pan_gesture(true, min, max), + _ => winit_window.recognize_pan_gesture(false, 0, 0), + } + } + + if window.prefers_home_indicator_hidden != cache.window.prefers_home_indicator_hidden { + winit_window + .set_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); + } + if window.prefers_status_bar_hidden != cache.window.prefers_status_bar_hidden { + winit_window.set_prefers_status_bar_hidden(window.prefers_status_bar_hidden); + } + if window.preferred_screen_edges_deferring_system_gestures + != cache + .window + .preferred_screen_edges_deferring_system_gestures + { + use crate::converters::convert_screen_edge; + let preferred_edge = + convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures); + winit_window.set_preferred_screen_edges_deferring_system_gestures(preferred_edge); + } + } + cache.window = window.clone(); } - cache.window = window.clone(); - } + }); } /// This keeps track of which keys are pressed on each window. diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index cd281103e4..8bf326f453 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,8 +1,8 @@ use bevy_a11y::AccessibilityRequested; use bevy_ecs::entity::Entity; -use bevy_ecs::entity::hash_map::EntityHashMap; -use bevy_platform_support::collections::HashMap; +use bevy_ecs::entity::EntityHashMap; +use bevy_platform::collections::HashMap; use bevy_window::{ CursorGrabMode, MonitorSelection, VideoModeSelection, Window, WindowMode, WindowPosition, WindowResolution, WindowWrapper, @@ -42,6 +42,16 @@ pub struct WinitWindows { } impl WinitWindows { + /// Creates a new instance of `WinitWindows`. + pub const fn new() -> Self { + Self { + windows: HashMap::new(), + entity_to_winit: EntityHashMap::new(), + winit_to_entity: HashMap::new(), + _not_send_sync: core::marker::PhantomData, + } + } + /// Creates a `winit` window and associates it with our entity. pub fn create_window( &mut self, @@ -119,7 +129,8 @@ impl WinitWindows { .with_resizable(window.resizable) .with_enabled_buttons(convert_enabled_buttons(window.enabled_buttons)) .with_decorations(window.decorations) - .with_transparent(window.transparent); + .with_transparent(window.transparent) + .with_active(window.focused); #[cfg(target_os = "windows")] { @@ -145,7 +156,14 @@ impl WinitWindows { #[cfg(target_os = "ios")] { + use crate::converters::convert_screen_edge; use winit::platform::ios::WindowAttributesExtIOS; + + let preferred_edge = + convert_screen_edge(window.preferred_screen_edges_deferring_system_gestures); + + winit_window_attributes = winit_window_attributes + .with_preferred_screen_edges_deferring_system_gestures(preferred_edge); winit_window_attributes = winit_window_attributes .with_prefers_home_indicator_hidden(window.prefers_home_indicator_hidden); winit_window_attributes = winit_window_attributes @@ -278,6 +296,7 @@ impl WinitWindows { let winit_window = event_loop.create_window(winit_window_attributes).unwrap(); let name = window.title.clone(); prepare_accessibility_for_window( + event_loop, &winit_window, entity, name, diff --git a/deny.toml b/deny.toml index 7d76c70de0..d22efdf153 100644 --- a/deny.toml +++ b/deny.toml @@ -8,6 +8,9 @@ ignore = [ # See: https://rustsec.org/advisories/RUSTSEC-2024-0436 # Bevy relies on this in multiple indirect ways, so ignoring it is the only feasible current solution "RUSTSEC-2024-0436", + # unmaintained: postcard -> heapless -> atomic-polyfill + # See https://github.com/jamesmunns/postcard/issues/223 + "RUSTSEC-2023-0089", ] [licenses] diff --git a/docs-template/EXAMPLE_README.md.tpl b/docs-template/EXAMPLE_README.md.tpl index a5438464b3..9cde78c3b9 100644 --- a/docs-template/EXAMPLE_README.md.tpl +++ b/docs-template/EXAMPLE_README.md.tpl @@ -269,6 +269,7 @@ Bevy has a helper to build its examples: - Build for WebGL2: `cargo run -p build-wasm-example -- --api webgl2 load_gltf` - Build for WebGPU: `cargo run -p build-wasm-example -- --api webgpu load_gltf` +- Debug: `cargo run -p build-wasm-example -- --debug --api webgl2 load_gltf` This helper will log the command used to build the examples. diff --git a/docs/cargo_features.md b/docs/cargo_features.md index a96c2697f5..1a1cb68fda 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -16,6 +16,7 @@ The default feature set enables most of the expected features of a game engine, |animation|Enable animation support, and glTF animation loading| |async_executor|Uses `async-executor` as a task execution backend.| |bevy_animation|Provides animation functionality| +|bevy_anti_aliasing|Provides various anti aliasing solutions| |bevy_asset|Provides asset functionality| |bevy_audio|Provides audio functionality| |bevy_color|Provides shared color types and operations| diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index d652cee1fd..a09bd629ff 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -94,7 +94,7 @@ export PKG_CONFIG_PATH="/usr/lib/x86_64-linux-gnu/pkgconfig/" ## Arch / Manjaro ```bash -sudo pacman -S libx11 pkgconf alsa-lib +sudo pacman -S libx11 pkgconf alsa-lib libxcursor libxrandr libxi ``` Install `pipewire-alsa` or `pulseaudio-alsa` depending on the sound server you are using. @@ -152,7 +152,7 @@ for more information about `devShells`. Note that this template does not add Rust to the environment because there are many ways to do it. For example, to use stable Rust from nixpkgs, you can add `cargo` and `rustc` to `nativeBuildInputs`. -[Here](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ju/jumpy/package.nix) +[Here]([https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/ju/jumpy/package.nix](https://github.com/NixOS/nixpkgs/blob/0da3c44a9460a26d2025ec3ed2ec60a895eb1114/pkgs/games/jumpy/default.nix)) is an example of packaging a Bevy program in nix. ## [OpenSUSE](https://www.opensuse.org/) diff --git a/docs/profiling.md b/docs/profiling.md index 6d7c37a8e9..ef6a46a608 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -9,6 +9,8 @@ - [Chrome tracing format](#chrome-tracing-format) - [Perf flame graph](#perf-flame-graph) - [GPU runtime](#gpu-runtime) + - [Vendor tools](#vendor-tools) + - [Tracy RenderQueue](#tracy-renderqueue) - [Compile time](#compile-time) ## CPU runtime @@ -124,6 +126,16 @@ After closing your app, an interactive `svg` file will be produced: ## GPU runtime +First, a quick note on how GPU programming works. GPUs are essentially separate computers with their own compiler, scheduler, memory (for discrete GPUs), etc. You do not simply call functions to have the GPU perform work - instead, you communicate with them by sending data back and forth over the PCIe bus, via the GPU driver. + +Specifically, you record a list of tasks (commands) for the GPU to perform into a CommandBuffer, and then submit that on a Queue to the GPU. At some point in the future, the GPU will receive the commands and execute them. + +In terms of where your app is spending time doing graphics work, it might manifest as a CPU bottleneck (extracting to the render world, wgpu resource tracking, recording commands to a CommandBuffer, or GPU driver code), as a GPU bottleneck (the GPU actually running your commands), or even as a data transfer bottleneck (uploading new assets or other data to the GPU over the PCIe bus). + +Graphics related work is not all CPU work or all GPU work, but a mix of both, and you should find the bottleneck and profile using the appropriate tool for each case. + +### Vendor tools + If CPU profiling has shown that GPU work is the bottleneck, it's time to profile the GPU. For profiling GPU work, you should use the tool corresponding to your GPU's vendor: @@ -135,15 +147,18 @@ For profiling GPU work, you should use the tool corresponding to your GPU's vend Note that while RenderDoc is a great debugging tool, it is _not_ a profiler, and should not be used for this purpose. -### Graphics work +### Tracy RenderQueue -Finally, a quick note on how GPU programming works. GPUs are essentially separate computers with their own compiler, scheduler, memory (for discrete GPUs), etc. You do not simply call functions to have the GPU perform work - instead, you communicate with them by sending data back and forth over the PCIe bus, via the GPU driver. +While it doesn't provide as much detail as vendor-specific tooling, Tracy can also be used to coarsely measure GPU performance. -Specifically, you record a list of tasks (commands) for the GPU to perform into a CommandBuffer, and then submit that on a Queue to the GPU. At some point in the future, the GPU will receive the commands and execute them. +When you compile with Bevy's `trace_tracy` feature, GPU spans will show up in a separate row at the top of Tracy, labeled as `RenderQueue`. -In terms of where your app is spending time doing graphics work, it might manifest as a CPU bottleneck (extracting to the render world, wgpu resource tracking, recording commands to a CommandBuffer, or GPU driver code), or it might manifest as a GPU bottleneck (the GPU actually running your commands). +> [!NOTE] +> Due to dynamic clock speeds, GPU timings will have large frame-to-frame variance, unless you use an external tool to lock your GPU clocks to base speeds. When measuring GPU performance via Tracy, only look at the MTPC column of Tracy's statistics panel, or the span distribution/median, and not at any individual frame data. + -Graphics related work is not all CPU work or all GPU work, but a mix of both, and you should find the bottleneck and profile using the appropriate tool for each case. +> [!NOTE] +> Unlike ECS systems, Bevy will not automatically add GPU profiling spans. You will need to add GPU timing spans yourself for any custom rendering work. See the [`RenderDiagnosticsPlugin`](https://docs.rs/bevy/latest/bevy/render/diagnostic/struct.RenderDiagnosticsPlugin.html) docs for more details. ## Compile time diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index bde8e7bb8c..f69e138364 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -1,4 +1,14 @@ -//! Shows how to render simple primitive shapes with a single color. +//! Here we use shape primitives to build meshes in a 2D rendering context, making each mesh a certain color by giving that mesh's entity a material based off a [`Color`]. +//! +//! Meshes are better known for their use in 3D rendering, but we can use them in a 2D context too. Without a third dimension, the meshes we're building are flat – like paper on a table. These are still very useful for "vector-style" graphics, picking behavior, or as a foundation to build off of for where to apply a shader. +//! +//! A "shape definition" is not a mesh on its own. A circle can be defined with a radius, i.e. [`Circle::new(50.0)`][Circle::new], but rendering tends to happen with meshes built out of triangles. So we need to turn shape descriptions into meshes. +//! +//! Thankfully, we can add shape primitives directly to [`Assets`] because [`Mesh`] implements [`From`] for shape primitives and [`Assets::add`] can be given any value that can be "turned into" `T`! +//! +//! We apply a material to the shape by first making a [`Color`] then calling [`Assets::add`] with that color as its argument, which will create a material from that color through the same process [`Assets::add`] can take a shape primitive. +//! +//! Both the mesh and material need to be wrapped in their own "newtypes". The mesh and material are currently [`Handle`] and [`Handle`] at the moment, which are not components. Handles are put behind "newtypes" to prevent ambiguity, as some entities might want to have handles to meshes (or images, or materials etc.) for different purposes! All we need to do to make them rendering-relevant components is wrap the mesh handle and the material handle in [`Mesh2d`] and [`MeshMaterial2d`] respectively. //! //! You can toggle wireframes with the space bar except on wasm. Wasm does not support //! `POLYGON_MODE_LINE` on the gpu. @@ -12,7 +22,7 @@ fn main() { app.add_plugins(( DefaultPlugins, #[cfg(not(target_arch = "wasm32"))] - Wireframe2dPlugin, + Wireframe2dPlugin::default(), )) .add_systems(Startup, setup); #[cfg(not(target_arch = "wasm32"))] diff --git a/examples/2d/2d_viewport_to_world.rs b/examples/2d/2d_viewport_to_world.rs index 9e58816cee..ce611bd4e9 100644 --- a/examples/2d/2d_viewport_to_world.rs +++ b/examples/2d/2d_viewport_to_world.rs @@ -15,10 +15,7 @@ fn main() { .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(FixedUpdate, controls) - .add_systems( - PostUpdate, - draw_cursor.after(TransformSystem::TransformPropagate), - ) + .add_systems(PostUpdate, draw_cursor.after(TransformSystems::Propagate)) .run(); } diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index fd90210d6f..9d9be1e5c7 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -25,7 +25,6 @@ fn setup( commands.spawn(( Camera2d, Camera { - hdr: true, // 1. HDR is required for bloom clear_color: ClearColorConfig::Custom(Color::BLACK), ..default() }, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index b6c27a2e62..6354aa468c 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -25,9 +25,10 @@ use bevy::{ SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState, TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, - sync_world::MainEntityHashMap, + sync_component::SyncComponentPlugin, + sync_world::{MainEntityHashMap, RenderEntity}, view::{ExtractedView, RenderVisibleEntities, ViewTarget}, - Extract, Render, RenderApp, RenderSet, + Extract, Render, RenderApp, RenderSystems, }, sprite::{ extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey, @@ -300,6 +301,7 @@ impl Plugin for ColoredMesh2dPlugin { &COLORED_MESH2D_SHADER_HANDLE, Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()), ); + app.add_plugins(SyncComponentPlugin::::default()); // Register our custom draw function, and add our render systems app.get_sub_app_mut(RenderApp) @@ -311,7 +313,10 @@ impl Plugin for ColoredMesh2dPlugin { ExtractSchedule, extract_colored_mesh2d.after(extract_mesh2d), ) - .add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::QueueMeshes)); + .add_systems( + Render, + queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes), + ); } fn finish(&self, app: &mut App) { @@ -329,12 +334,21 @@ pub fn extract_colored_mesh2d( // When extracting, you must use `Extract` to mark the `SystemParam`s // which should be taken from the main world. query: Extract< - Query<(Entity, &ViewVisibility, &GlobalTransform, &Mesh2d), With>, + Query< + ( + Entity, + RenderEntity, + &ViewVisibility, + &GlobalTransform, + &Mesh2d, + ), + With, + >, >, mut render_mesh_instances: ResMut, ) { let mut values = Vec::with_capacity(*previous_len); - for (entity, view_visibility, transform, handle) in &query { + for (entity, render_entity, view_visibility, transform, handle) in &query { if !view_visibility.get() { continue; } @@ -344,7 +358,7 @@ pub fn extract_colored_mesh2d( flags: MeshFlags::empty().bits(), }; - values.push((entity, ColoredMesh2d)); + values.push((render_entity, ColoredMesh2d)); render_mesh_instances.insert( entity.into(), RenderMesh2dInstance { diff --git a/examples/2d/sprite_scale.rs b/examples/2d/sprite_scale.rs index 036ff841b5..c549134419 100644 --- a/examples/2d/sprite_scale.rs +++ b/examples/2d/sprite_scale.rs @@ -132,7 +132,7 @@ fn setup_sprites(mut commands: Commands, asset_server: Res) { TextLayout::new_with_justify(JustifyText::Center), TextFont::from_font_size(15.), Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.), - bevy::sprite::Anchor::TopCenter, + bevy::sprite::Anchor::TOP_CENTER, )); }); } @@ -278,7 +278,7 @@ fn setup_texture_atlas( TextLayout::new_with_justify(JustifyText::Center), TextFont::from_font_size(15.), Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.), - bevy::sprite::Anchor::TopCenter, + bevy::sprite::Anchor::TOP_CENTER, )); }); } diff --git a/examples/2d/sprite_slice.rs b/examples/2d/sprite_slice.rs index a240b391e6..94f4fe809f 100644 --- a/examples/2d/sprite_slice.rs +++ b/examples/2d/sprite_slice.rs @@ -83,7 +83,7 @@ fn spawn_sprites( for (label, text_style, size, scale_mode) in cases { position.x += 0.5 * size.x; - let mut cmd = commands.spawn(( + commands.spawn(( Sprite { image: texture_handle.clone(), custom_size: Some(size), @@ -91,16 +91,14 @@ fn spawn_sprites( ..default() }, Transform::from_translation(position), - )); - cmd.with_children(|builder| { - builder.spawn(( + children![( Text2d::new(label), text_style, TextLayout::new_with_justify(JustifyText::Center), Transform::from_xyz(0., -0.5 * size.y - 10., 0.0), - bevy::sprite::Anchor::TopCenter, - )); - }); + bevy::sprite::Anchor::TOP_CENTER, + )], + )); position.x += 0.5 * size.x + gap; } } diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index 9fb13b0eb2..7b1abfd8da 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -72,41 +72,35 @@ fn setup(mut commands: Commands, asset_server: Res) { }; let box_size = Vec2::new(300.0, 200.0); let box_position = Vec2::new(0.0, -250.0); - commands - .spawn(( - Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), box_size), - Transform::from_translation(box_position.extend(0.0)), - )) - .with_children(|builder| { - builder.spawn(( - Text2d::new("this text wraps in the box\n(Unicode linebreaks)"), - slightly_smaller_text_font.clone(), - TextLayout::new(JustifyText::Left, LineBreak::WordBoundary), - // Wrap text in the rectangle - TextBounds::from(box_size), - // Ensure the text is drawn on top of the box - Transform::from_translation(Vec3::Z), - )); - }); + commands.spawn(( + Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), box_size), + Transform::from_translation(box_position.extend(0.0)), + children![( + Text2d::new("this text wraps in the box\n(Unicode linebreaks)"), + slightly_smaller_text_font.clone(), + TextLayout::new(JustifyText::Left, LineBreak::WordBoundary), + // Wrap text in the rectangle + TextBounds::from(box_size), + // Ensure the text is drawn on top of the box + Transform::from_translation(Vec3::Z), + )], + )); let other_box_size = Vec2::new(300.0, 200.0); let other_box_position = Vec2::new(320.0, -250.0); - commands - .spawn(( - Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), other_box_size), - Transform::from_translation(other_box_position.extend(0.0)), - )) - .with_children(|builder| { - builder.spawn(( - Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"), - slightly_smaller_text_font.clone(), - TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter), - // Wrap text in the rectangle - TextBounds::from(other_box_size), - // Ensure the text is drawn on top of the box - Transform::from_translation(Vec3::Z), - )); - }); + commands.spawn(( + Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), other_box_size), + Transform::from_translation(other_box_position.extend(0.0)), + children![( + Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"), + slightly_smaller_text_font.clone(), + TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter), + // Wrap text in the rectangle + TextBounds::from(other_box_size), + // Ensure the text is drawn on top of the box + Transform::from_translation(Vec3::Z), + )], + )); // Demonstrate font smoothing off commands.spawn(( @@ -129,10 +123,10 @@ fn setup(mut commands: Commands, asset_server: Res) { )) .with_children(|commands| { for (text_anchor, color) in [ - (Anchor::TopLeft, Color::Srgba(LIGHT_SALMON)), - (Anchor::TopRight, Color::Srgba(LIGHT_GREEN)), - (Anchor::BottomRight, Color::Srgba(LIGHT_BLUE)), - (Anchor::BottomLeft, Color::Srgba(LIGHT_YELLOW)), + (Anchor::TOP_LEFT, Color::Srgba(LIGHT_SALMON)), + (Anchor::TOP_RIGHT, Color::Srgba(LIGHT_GREEN)), + (Anchor::BOTTOM_RIGHT, Color::Srgba(LIGHT_BLUE)), + (Anchor::BOTTOM_LEFT, Color::Srgba(LIGHT_YELLOW)), ] { commands .spawn(( diff --git a/examples/2d/wireframe_2d.rs b/examples/2d/wireframe_2d.rs index fe4cc26187..aac599fcb0 100644 --- a/examples/2d/wireframe_2d.rs +++ b/examples/2d/wireframe_2d.rs @@ -31,7 +31,7 @@ fn main() { ..default() }), // You need to add this plugin to enable wireframe rendering - Wireframe2dPlugin, + Wireframe2dPlugin::default(), )) // Wireframes can be configured with this resource. This can be changed at runtime. .insert_resource(Wireframe2dConfig { diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 0346abd352..2ef3a65dc3 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -22,7 +22,7 @@ fn main() { .add_plugins(( DefaultPlugins.set(ImagePlugin::default_nearest()), #[cfg(not(target_arch = "wasm32"))] - WireframePlugin, + WireframePlugin::default(), )) .add_systems(Startup, setup) .add_systems( diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index f41ca3576f..1f693ce238 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -3,13 +3,13 @@ use std::{f32::consts::PI, fmt::Write}; use bevy::{ - core_pipeline::{ + anti_aliasing::{ contrast_adaptive_sharpening::ContrastAdaptiveSharpening, experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasing}, fxaa::{Fxaa, Sensitivity}, - prepass::{DepthPrepass, MotionVectorPrepass}, smaa::{Smaa, SmaaPreset}, }, + core_pipeline::prepass::{DepthPrepass, MotionVectorPrepass}, image::{ImageSampler, ImageSamplerDescriptor}, pbr::CascadeShadowConfigBuilder, prelude::*, @@ -17,6 +17,7 @@ use bevy::{ camera::TemporalJitter, render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, + view::Hdr, }, }; @@ -300,10 +301,7 @@ fn setup( // Camera commands.spawn(( Camera3d::default(), - Camera { - hdr: true, - ..default() - }, + Hdr, Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), ContrastAdaptiveSharpening { enabled: false, diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index 53c5c91dfa..edc6d04dab 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -20,11 +20,6 @@ fn main() { fn setup_camera_fog(mut commands: Commands) { commands.spawn(( Camera3d::default(), - // HDR is required for atmospheric scattering to be properly applied to the scene - Camera { - hdr: true, - ..default() - }, Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y), // This is the component that enables atmospheric scattering for a camera Atmosphere::EARTH, @@ -36,7 +31,7 @@ fn setup_camera_fog(mut commands: Commands) { scene_units_to_m: 1e+4, ..Default::default() }, - // The directional light illuminance used in this scene + // The directional light illuminance used in this scene // (the one recommended for use with this feature) is // quite bright, so raising the exposure compensation helps // bring the scene to a nicer brightness range. diff --git a/examples/3d/auto_exposure.rs b/examples/3d/auto_exposure.rs index 79fece61c8..62c875dc5d 100644 --- a/examples/3d/auto_exposure.rs +++ b/examples/3d/auto_exposure.rs @@ -40,10 +40,6 @@ fn setup( commands.spawn(( Camera3d::default(), - Camera { - hdr: true, - ..default() - }, Transform::from_xyz(1.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), AutoExposure { metering_mask: metering_mask.clone(), diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 830acfdb34..95fe522cf0 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -10,7 +10,7 @@ //! | `Spacebar` | Toggle Unlit | //! | `C` | Randomize Colors | -use bevy::{color::palettes::css::ORANGE, prelude::*}; +use bevy::{color::palettes::css::ORANGE, prelude::*, render::view::Hdr}; use rand::random; fn main() { @@ -149,6 +149,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_xyz(0.0, 2.5, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + Hdr, // Unfortunately, MSAA and HDR are not supported simultaneously under WebGL. // Since this example uses HDR, we must disable MSAA for Wasm builds, at least // until WebGPU is ready and no longer behind a feature flag in Web browsers. @@ -249,13 +250,23 @@ impl Default for ExampleState { fn example_control_system( mut materials: ResMut>, controllable: Query<(&MeshMaterial3d, &ExampleControls)>, - camera: Single<(&mut Camera, &mut Transform, &GlobalTransform), With>, + camera: Single< + ( + Entity, + &mut Camera, + &mut Transform, + &GlobalTransform, + Has, + ), + With, + >, mut labels: Query<(&mut Node, &ExampleLabel)>, mut display: Single<&mut Text, With>, labeled: Query<&GlobalTransform>, mut state: Local, time: Res

` +unsafe impl> SystemParamBuilder> + for WhenBuilder +{ + fn build(self, world: &mut World, meta: &mut SystemMeta) -> as SystemParam>::State { + self.0.build(world, meta) + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index f6e696a106..0faade39ee 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -2,12 +2,11 @@ use alloc::{borrow::Cow, format, vec::Vec}; use core::marker::PhantomData; use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, prelude::World, - query::Access, + query::{Access, FilteredAccessSet}, schedule::InternedSystemSet, - system::{input::SystemInput, SystemIn}, + system::{input::SystemInput, SystemIn, SystemParamValidationError}, world::unsafe_world_cell::UnsafeWorldCell, }; @@ -114,22 +113,20 @@ pub struct CombinatorSystem { a: A, b: B, name: Cow<'static, str>, - component_access: Access, - archetype_component_access: Access, + component_access_set: FilteredAccessSet, } impl CombinatorSystem { /// Creates a new system that combines two inner systems. /// /// The returned system will only be usable if `Func` implements [`Combine`]. - pub const fn new(a: A, b: B, name: Cow<'static, str>) -> Self { + pub fn new(a: A, b: B, name: Cow<'static, str>) -> Self { Self { _marker: PhantomData, a, b, name, - component_access: Access::new(), - archetype_component_access: Access::new(), + component_access_set: FilteredAccessSet::default(), } } } @@ -148,11 +145,11 @@ where } fn component_access(&self) -> &Access { - &self.component_access + self.component_access_set.combined_access() } - fn archetype_component_access(&self) -> &Access { - &self.archetype_component_access + fn component_access_set(&self) -> &FilteredAccessSet { + &self.component_access_set } fn is_send(&self) -> bool { @@ -176,29 +173,15 @@ where input, // SAFETY: The world accesses for both underlying systems have been registered, // so the caller will guarantee that no other systems will conflict with `a` or `b`. + // If either system has `is_exclusive()`, then the combined system also has `is_exclusive`. // Since these closures are `!Send + !Sync + !'static`, they can never be called // in parallel, so their world accesses will not conflict with each other. - // Additionally, `update_archetype_component_access` has been called, - // which forwards to the implementations for `self.a` and `self.b`. |input| unsafe { self.a.run_unsafe(input, world) }, // SAFETY: See the comment above. |input| unsafe { self.b.run_unsafe(input, world) }, ) } - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { - let world = world.as_unsafe_world_cell(); - Func::combine( - input, - // SAFETY: Since these closures are `!Send + !Sync + !'static`, they can never - // be called in parallel. Since mutable access to `world` only exists within - // the scope of either closure, we can be sure they will never alias one another. - |input| self.a.run(input, unsafe { world.world_mut() }), - // SAFETY: See the above safety comment. - |input| self.b.run(input, unsafe { world.world_mut() }), - ) - } - #[inline] fn apply_deferred(&mut self, world: &mut World) { self.a.apply_deferred(world); @@ -212,7 +195,10 @@ where } #[inline] - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { // SAFETY: Delegate to other `System` implementations. unsafe { self.a.validate_param_unsafe(world) } } @@ -220,18 +206,10 @@ where fn initialize(&mut self, world: &mut World) { self.a.initialize(world); self.b.initialize(world); - self.component_access.extend(self.a.component_access()); - self.component_access.extend(self.b.component_access()); - } - - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - self.a.update_archetype_component_access(world); - self.b.update_archetype_component_access(world); - - self.archetype_component_access - .extend(self.a.archetype_component_access()); - self.archetype_component_access - .extend(self.b.archetype_component_access()); + self.component_access_set + .extend(self.a.component_access_set().clone()); + self.component_access_set + .extend(self.b.component_access_set().clone()); } fn check_change_tick(&mut self, change_tick: Tick) { @@ -352,8 +330,7 @@ pub struct PipeSystem { a: A, b: B, name: Cow<'static, str>, - component_access: Access, - archetype_component_access: Access, + component_access_set: FilteredAccessSet, } impl PipeSystem @@ -363,13 +340,12 @@ where for<'a> B::In: SystemInput = A::Out>, { /// Creates a new system that pipes two inner systems. - pub const fn new(a: A, b: B, name: Cow<'static, str>) -> Self { + pub fn new(a: A, b: B, name: Cow<'static, str>) -> Self { Self { a, b, name, - component_access: Access::new(), - archetype_component_access: Access::new(), + component_access_set: FilteredAccessSet::default(), } } } @@ -388,11 +364,11 @@ where } fn component_access(&self) -> &Access { - &self.component_access + self.component_access_set.combined_access() } - fn archetype_component_access(&self) -> &Access { - &self.archetype_component_access + fn component_access_set(&self) -> &FilteredAccessSet { + &self.component_access_set } fn is_send(&self) -> bool { @@ -416,11 +392,6 @@ where self.b.run_unsafe(value, world) } - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { - let value = self.a.run(input, world); - self.b.run(value, world) - } - fn apply_deferred(&mut self, world: &mut World) { self.a.apply_deferred(world); self.b.apply_deferred(world); @@ -431,30 +402,36 @@ where self.b.queue_deferred(world); } - unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { - // SAFETY: Delegate to other `System` implementations. - unsafe { self.a.validate_param_unsafe(world) } - } + /// This method uses "early out" logic: if the first system fails validation, + /// the second system is not validated. + /// + /// Because the system validation is performed upfront, this can lead to situations + /// where later systems pass validation, but fail at runtime due to changes made earlier + /// in the piped systems. + // TODO: ensure that systems are only validated just before they are run. + // Fixing this will require fundamentally rethinking how piped systems work: + // they're currently treated as a single system from the perspective of the scheduler. + // See https://github.com/bevyengine/bevy/issues/18796 + unsafe fn validate_param_unsafe( + &mut self, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + // SAFETY: Delegate to the `System` implementation for `a`. + unsafe { self.a.validate_param_unsafe(world) }?; - fn validate_param(&mut self, world: &World) -> bool { - self.a.validate_param(world) && self.b.validate_param(world) + // SAFETY: Delegate to the `System` implementation for `b`. + unsafe { self.b.validate_param_unsafe(world) }?; + + Ok(()) } fn initialize(&mut self, world: &mut World) { self.a.initialize(world); self.b.initialize(world); - self.component_access.extend(self.a.component_access()); - self.component_access.extend(self.b.component_access()); - } - - fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { - self.a.update_archetype_component_access(world); - self.b.update_archetype_component_access(world); - - self.archetype_component_access - .extend(self.a.archetype_component_access()); - self.archetype_component_access - .extend(self.b.archetype_component_access()); + self.component_access_set + .extend(self.a.component_access_set().clone()); + self.component_access_set + .extend(self.b.component_access_set().clone()); } fn check_change_tick(&mut self, change_tick: Tick) { @@ -486,3 +463,27 @@ where for<'a> B::In: SystemInput = A::Out>, { } + +#[cfg(test)] +mod tests { + + #[test] + fn exclusive_system_piping_is_possible() { + use crate::prelude::*; + + fn my_exclusive_system(_world: &mut World) -> u32 { + 1 + } + + fn out_pipe(input: In) { + assert!(input.0 == 1); + } + + let mut world = World::new(); + + let mut schedule = Schedule::default(); + schedule.add_systems(my_exclusive_system.pipe(out_pipe)); + + schedule.run(&mut world); + } +} diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 7dd61946d6..af7b88edfc 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -1,5 +1,5 @@ -//! This module contains the definition of the [`Command`] trait, as well as -//! blanket implementations of the trait for closures. +//! Contains the definition of the [`Command`] trait, +//! as well as the blanket implementation of the trait for closures. //! //! It also contains functions that return closures for use with //! [`Commands`](crate::system::Commands). @@ -8,12 +8,12 @@ use crate::{ bundle::{Bundle, InsertMode, NoBundleEffect}, change_detection::MaybeLocation, entity::Entity, - error::{BevyError, Result}, + error::Result, event::{Event, Events}, observer::TriggerTargets, resource::Resource, schedule::ScheduleLabel, - system::{error_handler, IntoSystem, SystemId, SystemInput}, + system::{IntoSystem, SystemId, SystemInput}, world::{FromWorld, SpawnBatchIter, World}, }; @@ -63,52 +63,6 @@ where } } -/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into -/// a [`Command`] that internally handles an error if it occurs and returns `()`. -pub trait HandleError { - /// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into - /// a [`Command`] that internally handles an error if it occurs and returns `()`. - fn handle_error_with(self, error_handler: fn(&mut World, BevyError)) -> impl Command; - /// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into - /// a [`Command`] that internally handles an error if it occurs and returns `()`. - fn handle_error(self) -> impl Command - where - Self: Sized, - { - self.handle_error_with(error_handler::default()) - } -} - -impl HandleError> for C -where - C: Command>, - E: Into, -{ - fn handle_error_with(self, error_handler: fn(&mut World, BevyError)) -> impl Command { - move |world: &mut World| match self.apply(world) { - Ok(_) => {} - Err(err) => (error_handler)(world, err.into()), - } - } -} - -impl HandleError for C -where - C: Command, -{ - #[inline] - fn handle_error_with(self, _error_handler: fn(&mut World, BevyError)) -> impl Command { - self - } - #[inline] - fn handle_error(self) -> impl Command - where - Self: Sized, - { - self - } -} - /// A [`Command`] that consumes an iterator of [`Bundles`](Bundle) to spawn a series of entities. /// /// This is more efficient than spawning the entities individually. @@ -229,8 +183,10 @@ where } } -/// A [`Command`] that removes a system previously registered with -/// [`World::register_system_cached`]. +/// A [`Command`] that removes a system previously registered with one of the following: +/// - [`Commands::run_system_cached`](crate::system::Commands::run_system_cached) +/// - [`World::run_system_cached`] +/// - [`World::register_system_cached`] pub fn unregister_system_cached(system: S) -> impl Command where I: SystemInput + Send + 'static, diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 12161ba33d..317ad8476a 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -1,11 +1,10 @@ -//! This module contains the definition of the [`EntityCommand`] trait, as well as -//! blanket implementations of the trait for closures. +//! Contains the definition of the [`EntityCommand`] trait, +//! as well as the blanket implementation of the trait for closures. //! //! It also contains functions that return closures for use with //! [`EntityCommands`](crate::system::EntityCommands). use alloc::vec::Vec; -use core::fmt; use log::info; use crate::{ @@ -13,11 +12,10 @@ use crate::{ change_detection::MaybeLocation, component::{Component, ComponentId, ComponentInfo}, entity::{Entity, EntityClonerBuilder}, - error::Result, event::Event, relationship::RelationshipHookMode, - system::{command::HandleError, Command, IntoObserverSystem}, - world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World}, + system::IntoObserverSystem, + world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld}, }; use bevy_ptr::OwningPtr; @@ -84,53 +82,6 @@ pub trait EntityCommand: Send + 'static { /// Executes this command for the given [`Entity`]. fn apply(self, entity: EntityWorldMut) -> Out; } -/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that -/// internally runs the [`EntityCommand`] on that entity. -/// -// NOTE: This is a separate trait from `EntityCommand` because "result-returning entity commands" and -// "non-result returning entity commands" require different implementations, so they cannot be automatically -// implemented. And this isn't the type of implementation that we want to thrust on people implementing -// EntityCommand. -pub trait CommandWithEntity { - /// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that - /// internally runs the [`EntityCommand`] on that entity. - fn with_entity(self, entity: Entity) -> impl Command + HandleError; -} - -impl CommandWithEntity> for C -where - C: EntityCommand, -{ - fn with_entity( - self, - entity: Entity, - ) -> impl Command> - + HandleError> { - move |world: &mut World| -> Result<(), EntityMutableFetchError> { - let entity = world.get_entity_mut(entity)?; - self.apply(entity); - Ok(()) - } - } -} - -impl CommandWithEntity>> for C -where - C: EntityCommand>, - Err: fmt::Debug + fmt::Display + Send + Sync + 'static, -{ - fn with_entity( - self, - entity: Entity, - ) -> impl Command>> + HandleError>> - { - move |world: &mut World| { - let entity = world.get_entity_mut(entity)?; - self.apply(entity) - .map_err(EntityCommandError::CommandFailed) - } - } -} /// An error that occurs when running an [`EntityCommand`] on a specific entity. #[derive(thiserror::Error, Debug)] @@ -252,9 +203,10 @@ pub fn retain() -> impl EntityCommand { /// /// # Note /// -/// This will also despawn any [`Children`](crate::hierarchy::Children) entities, -/// and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured to despawn descendants. -/// This results in "recursive despawn" behavior. +/// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) +/// that is configured to despawn descendants. +/// +/// For example, this will recursively despawn [`Children`](crate::hierarchy::Children). #[track_caller] pub fn despawn() -> impl EntityCommand { let caller = MaybeLocation::caller(); @@ -276,6 +228,7 @@ pub fn observe( } /// An [`EntityCommand`] that sends a [`Trigger`](crate::observer::Trigger) targeting an entity. +/// /// This will run any [`Observer`](crate::observer::Observer) of the given [`Event`] watching the entity. #[track_caller] pub fn trigger(event: impl Event) -> impl EntityCommand { diff --git a/crates/bevy_ecs/src/system/commands/error_handler.rs b/crates/bevy_ecs/src/system/commands/error_handler.rs deleted file mode 100644 index e5895f91bb..0000000000 --- a/crates/bevy_ecs/src/system/commands/error_handler.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! This module contains convenience functions that return simple error handlers -//! for use with [`Commands::queue_handled`](super::Commands::queue_handled) and [`EntityCommands::queue_handled`](super::EntityCommands::queue_handled). - -use crate::{error::BevyError, world::World}; -use log::{error, warn}; - -/// An error handler that does nothing. -pub fn silent() -> fn(&mut World, BevyError) { - |_, _| {} -} - -/// An error handler that accepts an error and logs it with [`warn!`]. -pub fn warn() -> fn(&mut World, BevyError) { - |_, error| warn!("{error}") -} - -/// An error handler that accepts an error and logs it with [`error!`]. -pub fn error() -> fn(&mut World, BevyError) { - |_, error| error!("{error}") -} - -/// An error handler that accepts an error and panics with the error in -/// the panic message. -pub fn panic() -> fn(&mut World, BevyError) { - |_, error| panic!("{error}") -} - -/// The default error handler. This defaults to [`panic()`]. If the -/// `configurable_error_handler` cargo feature is enabled, then -/// `GLOBAL_ERROR_HANDLER` will be used instead, enabling error handler customization. -#[cfg(not(feature = "configurable_error_handler"))] -#[inline] -pub fn default() -> fn(&mut World, BevyError) { - panic() -} - -/// A global error handler. This can be set at startup, as long as it is set before -/// any uses. This should generally be configured _before_ initializing the app. -/// -/// If the `configurable_error_handler` cargo feature is enabled, this will be used -/// by default. -/// -/// This should be set in the following way: -/// -/// ``` -/// # use bevy_ecs::system::error_handler::{GLOBAL_ERROR_HANDLER, warn}; -/// GLOBAL_ERROR_HANDLER.set(warn()); -/// // initialize Bevy App here -/// ``` -#[cfg(feature = "configurable_error_handler")] -pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock = - std::sync::OnceLock::new(); - -/// The default error handler. This defaults to [`panic()`]. If the -/// `configurable_error_handler` cargo feature is enabled, then -/// [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization. -#[cfg(feature = "configurable_error_handler")] -#[inline] -pub fn default() -> fn(&mut World, BevyError) { - *GLOBAL_ERROR_HANDLER.get_or_init(|| panic()) -} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d91154d985..3012a65458 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,6 +1,5 @@ pub mod command; pub mod entity_command; -pub mod error_handler; #[cfg(feature = "std")] mod parallel_scope; @@ -13,7 +12,6 @@ pub use parallel_scope::*; use alloc::boxed::Box; use core::marker::PhantomData; -use log::error; use crate::{ self as bevy_ecs, @@ -21,14 +19,14 @@ use crate::{ change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, - error::BevyError, + error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError}, event::Event, observer::{Observer, TriggerTargets}, resource::Resource, schedule::ScheduleLabel, system::{ - command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred, - IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, + Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, SystemInput, + SystemParamValidationError, }, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue, @@ -60,7 +58,6 @@ use crate::{ /// /// ``` /// # use bevy_ecs::prelude::*; -/// # /// fn my_system(mut commands: Commands) { /// // ... /// } @@ -89,20 +86,15 @@ use crate::{ /// # Error handling /// /// A [`Command`] can return a [`Result`](crate::error::Result), -/// which will be passed to an error handler if the `Result` is an error. +/// which will be passed to an [error handler](crate::error) if the `Result` is an error. /// -/// Error handlers are functions/closures of the form `fn(&mut World, Error)`. -/// They are granted exclusive access to the [`World`], which enables them to -/// respond to the error in whatever way is necessary. -/// -/// The [default error handler](error_handler::default) panics. -/// It can be configured by enabling the `configurable_error_handler` cargo feature, -/// then setting the `GLOBAL_ERROR_HANDLER`. +/// The default error handler panics. It can be configured via +/// the [`DefaultErrorHandler`](crate::error::DefaultErrorHandler) resource. /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`Commands::queue_handled`]. /// -/// The [`error_handler`] module provides some simple error handlers for convenience. +/// The [`error`](crate::error) module provides some simple error handlers for convenience. /// /// [`ApplyDeferred`]: crate::schedule::ApplyDeferred pub struct Commands<'w, 's> { @@ -140,21 +132,6 @@ const _: () = { } } - unsafe fn new_archetype( - state: &mut Self::State, - archetype: &bevy_ecs::archetype::Archetype, - system_meta: &mut bevy_ecs::system::SystemMeta, - ) { - // SAFETY: Caller guarantees the archetype is from the world used in `init_state` - unsafe { - <__StructFieldsAlias<'_, '_> as bevy_ecs::system::SystemParam>::new_archetype( - &mut state.state, - archetype, - system_meta, - ); - }; - } - fn apply( state: &mut Self::State, system_meta: &bevy_ecs::system::SystemMeta, @@ -181,12 +158,12 @@ const _: () = { #[inline] unsafe fn validate_param( - state: &Self::State, + state: &mut Self::State, system_meta: &bevy_ecs::system::SystemMeta, world: UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { <(Deferred, &Entities) as bevy_ecs::system::SystemParam>::validate_param( - &state.state, + &mut state.state, system_meta, world, ) @@ -222,19 +199,11 @@ enum InternalQueue<'s> { impl<'w, 's> Commands<'w, 's> { /// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`]. - /// - /// It is not required to call this constructor when using `Commands` as a [system parameter]. - /// - /// [system parameter]: crate::system::SystemParam pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self { Self::new_from_entities(queue, &world.entities) } /// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference. - /// - /// It is not required to call this constructor when using `Commands` as a [system parameter]. - /// - /// [system parameter]: crate::system::SystemParam pub fn new_from_entities(queue: &'s mut CommandQueue, entities: &'w Entities) -> Self { Self { queue: InternalQueue::CommandQueue(Deferred(queue)), @@ -248,7 +217,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// # Safety /// - /// * Caller ensures that `queue` must outlive 'w + /// * Caller ensures that `queue` must outlive `'w` pub(crate) unsafe fn new_raw_from_entities( queue: RawCommandQueue, entities: &'w Entities, @@ -260,9 +229,10 @@ impl<'w, 's> Commands<'w, 's> { } /// Returns a [`Commands`] with a smaller lifetime. + /// /// This is useful if you have `&mut Commands` but need `Commands`. /// - /// # Examples + /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; @@ -289,7 +259,7 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Take all commands from `other` and append them to `self`, leaving `other` empty + /// Take all commands from `other` and append them to `self`, leaving `other` empty. pub fn append(&mut self, other: &mut CommandQueue) { match &mut self.queue { InternalQueue::CommandQueue(queue) => queue.bytes.append(&mut other.bytes), @@ -300,15 +270,12 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Reserves a new empty [`Entity`] to be spawned, and returns its corresponding [`EntityCommands`]. - /// - /// See [`World::spawn_empty`] for more details. + /// Spawns a new empty [`Entity`] and returns its corresponding [`EntityCommands`]. /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// /// #[derive(Component)] /// struct Label(&'static str); /// #[derive(Component)] @@ -317,14 +284,14 @@ impl<'w, 's> Commands<'w, 's> { /// struct Agility(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create a new empty entity and retrieve its id. - /// let empty_entity = commands.spawn_empty().id(); + /// // Create a new empty entity. + /// commands.spawn_empty(); /// - /// // Create another empty entity, then add some component to it + /// // Create another empty entity. /// commands.spawn_empty() - /// // adds a new component bundle to the entity + /// // Add a new component bundle to the entity. /// .insert((Strength(1), Agility(2))) - /// // adds a single component to the entity + /// // Add a single component to the entity. /// .insert(Label("hello world")); /// } /// # bevy_ecs::system::assert_is_system(example_system); @@ -332,61 +299,62 @@ impl<'w, 's> Commands<'w, 's> { /// /// # See also /// - /// - [`spawn`](Self::spawn) to spawn an entity with a bundle. - /// - [`spawn_batch`](Self::spawn_batch) to spawn entities with a bundle each. + /// - [`spawn`](Self::spawn) to spawn an entity with components. + /// - [`spawn_batch`](Self::spawn_batch) to spawn many entities + /// with the same combination of components. + #[track_caller] pub fn spawn_empty(&mut self) -> EntityCommands { let entity = self.entities.reserve_entity(); - EntityCommands { + let mut entity_commands = EntityCommands { entity, commands: self.reborrow(), - } + }; + let caller = MaybeLocation::caller(); + entity_commands.queue(move |entity: EntityWorldMut| { + let index = entity.id().index(); + let world = entity.into_world_mut(); + let tick = world.change_tick(); + // SAFETY: Entity has been flushed + unsafe { + world.entities_mut().mark_spawn_despawn(index, caller, tick); + } + }); + entity_commands } - /// Pushes a [`Command`] to the queue for creating a new entity with the given [`Bundle`]'s components, - /// and returns its corresponding [`EntityCommands`]. + /// Spawns a new [`Entity`] with the given components + /// and returns the entity's corresponding [`EntityCommands`]. /// - /// In case multiple bundles of the same [`Bundle`] type need to be spawned, - /// [`spawn_batch`](Self::spawn_batch) should be used for better performance. + /// To spawn many entities with the same combination of components, + /// [`spawn_batch`](Self::spawn_batch) can be used for better performance. /// /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; - /// + /// # use bevy_ecs::prelude::*; /// #[derive(Component)] - /// struct Component1; + /// struct ComponentA(u32); /// #[derive(Component)] - /// struct Component2; - /// #[derive(Component)] - /// struct Label(&'static str); - /// #[derive(Component)] - /// struct Strength(u32); - /// #[derive(Component)] - /// struct Agility(u32); + /// struct ComponentB(u32); /// /// #[derive(Bundle)] /// struct ExampleBundle { - /// a: Component1, - /// b: Component2, + /// a: ComponentA, + /// b: ComponentB, /// } /// /// fn example_system(mut commands: Commands) { /// // Create a new entity with a single component. - /// commands.spawn(Component1); + /// commands.spawn(ComponentA(1)); + /// + /// // Create a new entity with two components using a "tuple bundle". + /// commands.spawn((ComponentA(2), ComponentB(1))); /// /// // Create a new entity with a component bundle. /// commands.spawn(ExampleBundle { - /// a: Component1, - /// b: Component2, + /// a: ComponentA(3), + /// b: ComponentB(2), /// }); - /// - /// commands - /// // Create a new entity with two components using a "tuple bundle". - /// .spawn((Component1, Component2)) - /// // `spawn returns a builder, so you can insert more bundles like this: - /// .insert((Strength(1), Agility(2))) - /// // or insert single components like this: - /// .insert(Label("hello world")); /// } /// # bevy_ecs::system::assert_is_system(example_system); /// ``` @@ -394,15 +362,42 @@ impl<'w, 's> Commands<'w, 's> { /// # See also /// /// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components. - /// - [`spawn_batch`](Self::spawn_batch) to spawn entities with a bundle each. + /// - [`spawn_batch`](Self::spawn_batch) to spawn many entities + /// with the same combination of components. #[track_caller] pub fn spawn(&mut self, bundle: T) -> EntityCommands { - let mut entity = self.spawn_empty(); - entity.insert(bundle); - entity + let entity = self.entities.reserve_entity(); + let mut entity_commands = EntityCommands { + entity, + commands: self.reborrow(), + }; + let caller = MaybeLocation::caller(); + + entity_commands.queue(move |mut entity: EntityWorldMut| { + // Store metadata about the spawn operation. + // This is the same as in `spawn_empty`, but merged into + // the same command for better performance. + let index = entity.id().index(); + entity.world_scope(|world| { + let tick = world.change_tick(); + // SAFETY: Entity has been flushed + unsafe { + world.entities_mut().mark_spawn_despawn(index, caller, tick); + } + }); + + entity.insert_with_caller( + bundle, + InsertMode::Replace, + caller, + crate::relationship::RelationshipHookMode::Run, + ); + }); + // entity_command::insert(bundle, InsertMode::Replace) + entity_commands } - /// Returns the [`EntityCommands`] for the requested [`Entity`]. + /// Returns the [`EntityCommands`] for the given [`Entity`]. /// /// This method does not guarantee that commands queued by the returned `EntityCommands` /// will be successful, since the entity could be despawned before they are executed. @@ -410,24 +405,18 @@ impl<'w, 's> Commands<'w, 's> { /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource)] + /// struct PlayerEntity { + /// entity: Entity + /// } /// /// #[derive(Component)] /// struct Label(&'static str); - /// #[derive(Component)] - /// struct Strength(u32); - /// #[derive(Component)] - /// struct Agility(u32); /// - /// fn example_system(mut commands: Commands) { - /// // Create a new, empty entity - /// let entity = commands.spawn_empty().id(); - /// - /// commands.entity(entity) - /// // adds a new component bundle to the entity - /// .insert((Strength(1), Agility(2))) - /// // adds a single component to the entity - /// .insert(Label("hello world")); + /// fn example_system(mut commands: Commands, player: Res) { + /// // Get the entity and add a component. + /// commands.entity(player.entity).insert(Label("hello world")); /// } /// # bevy_ecs::system::assert_is_system(example_system); /// ``` @@ -444,7 +433,7 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Returns the [`EntityCommands`] for the requested [`Entity`], if it exists. + /// Returns the [`EntityCommands`] for the requested [`Entity`] if it exists. /// /// This method does not guarantee that commands queued by the returned `EntityCommands` /// will be successful, since the entity could be despawned before they are executed. @@ -456,23 +445,25 @@ impl<'w, 's> Commands<'w, 's> { /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource)] + /// struct PlayerEntity { + /// entity: Entity + /// } /// /// #[derive(Component)] /// struct Label(&'static str); - /// fn example_system(mut commands: Commands) -> Result { - /// // Create a new, empty entity. - /// let entity = commands.spawn_empty().id(); /// - /// // Get the entity if it still exists, which it will in this case. - /// // If it didn't, the `?` operator would propagate the returned error - /// // to the system, and the system would pass it to an error handler. - /// let mut entity_commands = commands.get_entity(entity)?; + /// fn example_system(mut commands: Commands, player: Res) -> Result { + /// // Get the entity if it still exists and store the `EntityCommands`. + /// // If it doesn't exist, the `?` operator will propagate the returned error + /// // to the system, and the system will pass it to an error handler. + /// let mut entity_commands = commands.get_entity(player.entity)?; /// - /// // Add a single component to the entity. + /// // Add a component to the entity. /// entity_commands.insert(Label("hello world")); /// - /// // Return from the system with a success. + /// // Return from the system successfully. /// Ok(()) /// } /// # bevy_ecs::system::assert_is_system(example_system); @@ -497,57 +488,50 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Pushes a [`Command`] to the queue for creating entities with a particular [`Bundle`] type. + /// Spawns multiple entities with the same combination of components, + /// based on a batch of [`Bundles`](Bundle). /// - /// `bundles_iter` is a type that can be converted into a [`Bundle`] iterator - /// (it can also be a collection). + /// A batch can be any type that implements [`IntoIterator`] and contains bundles, + /// such as a [`Vec`](alloc::vec::Vec) or an array `[Bundle; N]`. /// - /// This method is equivalent to iterating `bundles_iter` - /// and calling [`spawn`](Self::spawn) on each bundle, - /// but it is faster due to memory pre-allocation. + /// This method is equivalent to iterating the batch + /// and calling [`spawn`](Self::spawn) for each bundle, + /// but is faster by pre-allocating memory and having exclusive [`World`] access. /// /// # Example /// /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component)] - /// # struct Name(String); - /// # #[derive(Component)] - /// # struct Score(u32); - /// # - /// # fn system(mut commands: Commands) { - /// commands.spawn_batch(vec![ - /// ( - /// Name("Alice".to_string()), - /// Score(0), - /// ), - /// ( - /// Name("Bob".to_string()), - /// Score(0), - /// ), - /// ]); - /// # } - /// # bevy_ecs::system::assert_is_system(system); + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Component)] + /// struct Score(u32); + /// + /// fn example_system(mut commands: Commands) { + /// commands.spawn_batch([ + /// (Name::new("Alice"), Score(0)), + /// (Name::new("Bob"), Score(0)), + /// ]); + /// } + /// # bevy_ecs::system::assert_is_system(example_system); /// ``` /// /// # See also /// - /// - [`spawn`](Self::spawn) to spawn an entity with a bundle. - /// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without any components. + /// - [`spawn`](Self::spawn) to spawn an entity with components. + /// - [`spawn_empty`](Self::spawn_empty) to spawn an entity without components. #[track_caller] - pub fn spawn_batch(&mut self, bundles_iter: I) + pub fn spawn_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, I::Item: Bundle, { - self.queue(command::spawn_batch(bundles_iter)); + self.queue(command::spawn_batch(batch)); } /// Pushes a generic [`Command`] to the command queue. /// /// If the [`Command`] returns a [`Result`], - /// it will be handled using the [default error handler](error_handler::default). + /// it will be handled using the [default error handler](crate::error::DefaultErrorHandler). /// /// To use a custom error handler, see [`Commands::queue_handled`]. /// @@ -578,6 +562,7 @@ impl<'w, 's> Commands<'w, 's> { /// fn add_three_to_counter_system(mut commands: Commands) { /// commands.queue(AddToCounter("3".to_string())); /// } + /// /// fn add_twenty_five_to_counter_system(mut commands: Commands) { /// commands.queue(|world: &mut World| { /// let mut counter = world.get_resource_or_insert_with(Counter::default); @@ -609,7 +594,8 @@ impl<'w, 's> Commands<'w, 's> { /// /// ``` /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::error_handler; + /// use bevy_ecs::error::warn; + /// /// #[derive(Resource, Default)] /// struct Counter(u64); /// @@ -625,8 +611,9 @@ impl<'w, 's> Commands<'w, 's> { /// } /// /// fn add_three_to_counter_system(mut commands: Commands) { - /// commands.queue_handled(AddToCounter("3".to_string()), error_handler::warn()); + /// commands.queue_handled(AddToCounter("3".to_string()), warn); /// } + /// /// fn add_twenty_five_to_counter_system(mut commands: Commands) { /// commands.queue(|world: &mut World| { /// let mut counter = world.get_resource_or_insert_with(Counter::default); @@ -639,7 +626,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn queue_handled + HandleError, T>( &mut self, command: C, - error_handler: fn(&mut World, BevyError), + error_handler: fn(BevyError, ErrorContext), ) { self.queue_internal(command.handle_error_with(error_handler)); } @@ -659,74 +646,27 @@ impl<'w, 's> Commands<'w, 's> { } } - /// Pushes a [`Command`] to the queue for creating entities, if needed, - /// and for adding a bundle to each entity. + /// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with, + /// based on a batch of `(Entity, Bundle)` pairs. /// - /// `bundles_iter` is a type that can be converted into an ([`Entity`], [`Bundle`]) iterator - /// (it can also be a collection). + /// A batch can be any type that implements [`IntoIterator`] + /// and contains `(Entity, Bundle)` tuples, + /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) + /// or an array `[(Entity, Bundle); N]`. /// - /// When the command is applied, - /// for each (`Entity`, `Bundle`) pair in the given `bundles_iter`, - /// the `Entity` is spawned, if it does not exist already. - /// Then, the `Bundle` is added to the entity. + /// This will overwrite any pre-existing components shared by the [`Bundle`] type. + /// Use [`Commands::insert_batch_if_new`] to keep the pre-existing components instead. /// - /// This method is equivalent to iterating `bundles_iter`, - /// calling [`spawn`](Self::spawn) for each bundle, - /// and passing it to [`insert`](EntityCommands::insert), - /// but it is faster due to memory pre-allocation. + /// This method is equivalent to iterating the batch + /// and calling [`insert`](EntityCommands::insert) for each pair, + /// but is faster by caching data that is shared between entities. /// - /// # Note + /// # Fallible /// - /// Spawning a specific `entity` value is rarely the right choice. Most apps should use [`Commands::spawn_batch`]. - /// This method should generally only be used for sharing entities across apps, and only when they have a scheme - /// worked out to share an ID space (which doesn't happen by default). - #[track_caller] - #[deprecated( - note = "This can cause extreme performance problems when used with lots of arbitrary free entities. See #18054 on GitHub." - )] - pub fn insert_or_spawn_batch(&mut self, bundles_iter: I) - where - I: IntoIterator + Send + Sync + 'static, - B: Bundle, - { - let caller = MaybeLocation::caller(); - self.queue(move |world: &mut World| { - - #[expect( - deprecated, - reason = "This needs to be supported for now, and the outer item is deprecated too." - )] - if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( - bundles_iter, - caller, - ) { - error!( - "{caller}: Failed to 'insert or spawn' bundle of type {} into the following invalid entities: {:?}", - core::any::type_name::(), - invalid_entities - ); - } - }); - } - - /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). + /// This command will fail if any of the given entities do not exist. /// - /// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples, - /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`. - /// - /// When the command is applied, for each `(Entity, Bundle)` pair in the given batch, - /// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`. - /// - /// This method is equivalent to iterating the batch, - /// calling [`entity`](Self::entity) for each pair, - /// and passing the bundle to [`insert`](EntityCommands::insert), - /// but it is faster due to memory pre-allocation. - /// - /// # Panics - /// - /// This command panics if any of the given entities do not exist. - /// - /// For the non-panicking version, see [`try_insert_batch`](Self::try_insert_batch). + /// It will internally return a [`TryInsertBatchError`](crate::world::error::TryInsertBatchError), + /// which will be handled by the [default error handler](crate::error::DefaultErrorHandler). #[track_caller] pub fn insert_batch(&mut self, batch: I) where @@ -736,24 +676,28 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::insert_batch(batch, InsertMode::Replace)); } - /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). + /// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with, + /// based on a batch of `(Entity, Bundle)` pairs. /// - /// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples, - /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`. + /// A batch can be any type that implements [`IntoIterator`] + /// and contains `(Entity, Bundle)` tuples, + /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) + /// or an array `[(Entity, Bundle); N]`. /// - /// When the command is applied, for each `(Entity, Bundle)` pair in the given batch, - /// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`. + /// This will keep any pre-existing components shared by the [`Bundle`] type + /// and discard the new values. + /// Use [`Commands::insert_batch`] to overwrite the pre-existing components instead. /// - /// This method is equivalent to iterating the batch, - /// calling [`entity`](Self::entity) for each pair, - /// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new), - /// but it is faster due to memory pre-allocation. + /// This method is equivalent to iterating the batch + /// and calling [`insert_if_new`](EntityCommands::insert_if_new) for each pair, + /// but is faster by caching data that is shared between entities. /// - /// # Panics + /// # Fallible /// - /// This command panics if any of the given entities do not exist. + /// This command will fail if any of the given entities do not exist. /// - /// For the non-panicking version, see [`try_insert_batch_if_new`](Self::try_insert_batch_if_new). + /// It will internally return a [`TryInsertBatchError`](crate::world::error::TryInsertBatchError), + /// which will be handled by the [default error handler](crate::error::DefaultErrorHandler). #[track_caller] pub fn insert_batch_if_new(&mut self, batch: I) where @@ -763,83 +707,88 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::insert_batch(batch, InsertMode::Keep)); } - /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). + /// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with, + /// based on a batch of `(Entity, Bundle)` pairs. /// - /// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples, - /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`. + /// A batch can be any type that implements [`IntoIterator`] + /// and contains `(Entity, Bundle)` tuples, + /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) + /// or an array `[(Entity, Bundle); N]`. /// - /// When the command is applied, for each `(Entity, Bundle)` pair in the given batch, - /// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`. + /// This will overwrite any pre-existing components shared by the [`Bundle`] type. + /// Use [`Commands::try_insert_batch_if_new`] to keep the pre-existing components instead. /// - /// This method is equivalent to iterating the batch, - /// calling [`get_entity`](Self::get_entity) for each pair, - /// and passing the bundle to [`insert`](EntityCommands::insert), - /// but it is faster due to memory pre-allocation. + /// This method is equivalent to iterating the batch + /// and calling [`insert`](EntityCommands::insert) for each pair, + /// but is faster by caching data that is shared between entities. /// - /// This command will send a warning if any of the given entities do not exist. + /// # Fallible /// - /// For the panicking version, see [`insert_batch`](Self::insert_batch). + /// This command will fail if any of the given entities do not exist. + /// + /// It will internally return a [`TryInsertBatchError`](crate::world::error::TryInsertBatchError), + /// which will be handled by [logging the error at the `warn` level](warn). #[track_caller] pub fn try_insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue( - command::insert_batch(batch, InsertMode::Replace) - .handle_error_with(error_handler::warn()), - ); + self.queue(command::insert_batch(batch, InsertMode::Replace).handle_error_with(warn)); } - /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). + /// Adds a series of [`Bundles`](Bundle) to each [`Entity`] they are paired with, + /// based on a batch of `(Entity, Bundle)` pairs. /// - /// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples, - /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`. + /// A batch can be any type that implements [`IntoIterator`] + /// and contains `(Entity, Bundle)` tuples, + /// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) + /// or an array `[(Entity, Bundle); N]`. /// - /// When the command is applied, for each `(Entity, Bundle)` pair in the given batch, - /// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`. + /// This will keep any pre-existing components shared by the [`Bundle`] type + /// and discard the new values. + /// Use [`Commands::try_insert_batch`] to overwrite the pre-existing components instead. /// - /// This method is equivalent to iterating the batch, - /// calling [`get_entity`](Self::get_entity) for each pair, - /// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new), - /// but it is faster due to memory pre-allocation. + /// This method is equivalent to iterating the batch + /// and calling [`insert_if_new`](EntityCommands::insert_if_new) for each pair, + /// but is faster by caching data that is shared between entities. /// - /// This command will send a warning if any of the given entities do not exist. + /// # Fallible /// - /// For the panicking version, see [`insert_batch_if_new`](Self::insert_batch_if_new). + /// This command will fail if any of the given entities do not exist. + /// + /// It will internally return a [`TryInsertBatchError`](crate::world::error::TryInsertBatchError), + /// which will be handled by [logging the error at the `warn` level](warn). #[track_caller] pub fn try_insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue( - command::insert_batch(batch, InsertMode::Keep).handle_error_with(error_handler::warn()), - ); + self.queue(command::insert_batch(batch, InsertMode::Keep).handle_error_with(warn)); } - /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. + /// Inserts a [`Resource`] into the [`World`] with an inferred value. /// /// The inferred value is determined by the [`FromWorld`] trait of the resource. - /// When the command is applied, - /// if the resource already exists, nothing happens. + /// Note that any resource with the [`Default`] trait automatically implements [`FromWorld`], + /// and those default values will be used. /// - /// See [`World::init_resource`] for more details. + /// If the resource already exists when the command is applied, nothing happens. /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource, Default)] - /// # struct Scoreboard { - /// # current_score: u32, - /// # high_score: u32, - /// # } - /// # - /// # fn initialize_scoreboard(mut commands: Commands) { - /// commands.init_resource::(); - /// # } + /// #[derive(Resource, Default)] + /// struct Scoreboard { + /// current_score: u32, + /// high_score: u32, + /// } + /// + /// fn initialize_scoreboard(mut commands: Commands) { + /// commands.init_resource::(); + /// } /// # bevy_ecs::system::assert_is_system(initialize_scoreboard); /// ``` #[track_caller] @@ -847,29 +796,26 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::init_resource::()); } - /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value. + /// Inserts a [`Resource`] into the [`World`] with a specific value. /// /// This will overwrite any previous value of the same resource type. /// - /// See [`World::insert_resource`] for more details. - /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource)] - /// # struct Scoreboard { - /// # current_score: u32, - /// # high_score: u32, - /// # } - /// # - /// # fn system(mut commands: Commands) { - /// commands.insert_resource(Scoreboard { - /// current_score: 0, - /// high_score: 0, - /// }); - /// # } + /// #[derive(Resource)] + /// struct Scoreboard { + /// current_score: u32, + /// high_score: u32, + /// } + /// + /// fn system(mut commands: Commands) { + /// commands.insert_resource(Scoreboard { + /// current_score: 0, + /// high_score: 0, + /// }); + /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` #[track_caller] @@ -877,24 +823,21 @@ impl<'w, 's> Commands<'w, 's> { self.queue(command::insert_resource(resource)); } - /// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`]. - /// - /// See [`World::remove_resource`] for more details. + /// Removes a [`Resource`] from the [`World`]. /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource)] - /// # struct Scoreboard { - /// # current_score: u32, - /// # high_score: u32, - /// # } - /// # - /// # fn system(mut commands: Commands) { - /// commands.remove_resource::(); - /// # } + /// #[derive(Resource)] + /// struct Scoreboard { + /// current_score: u32, + /// high_score: u32, + /// } + /// + /// fn system(mut commands: Commands) { + /// commands.remove_resource::(); + /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` pub fn remove_resource(&mut self) { @@ -902,55 +845,82 @@ impl<'w, 's> Commands<'w, 's> { } /// 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. + /// Before running a system, it must first be registered via + /// [`Commands::register_system`] or [`World::register_system`]. /// - /// Calls [`World::run_system`](World::run_system). + /// The system is run in an exclusive and single-threaded way. + /// Running slow systems can become a bottleneck. /// /// 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`] instead of running the system as a command. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). pub fn run_system(&mut self, id: SystemId) { - self.queue(command::run_system(id).handle_error_with(error_handler::warn())); + self.queue(command::run_system(id).handle_error_with(warn)); } - /// 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. + /// Runs the system corresponding to the given [`SystemId`] with input. + /// Before running a system, it must first be registered via + /// [`Commands::register_system`] or [`World::register_system`]. /// - /// Calls [`World::run_system_with`](World::run_system_with). + /// The system is run in an exclusive and single-threaded way. + /// Running slow systems can become a bottleneck. /// /// 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`] instead of running the system as a command. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). pub fn run_system_with(&mut self, id: SystemId, input: I::Inner<'static>) where I: SystemInput: Send> + 'static, { - self.queue(command::run_system_with(id, input).handle_error_with(error_handler::warn())); + self.queue(command::run_system_with(id, input).handle_error_with(warn)); } - /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. - /// - /// It's possible to register the same systems more than once, they'll be stored separately. + /// Registers a system and returns its [`SystemId`] so it can later be called by + /// [`Commands::run_system`] or [`World::run_system`]. /// /// This is different from adding systems to a [`Schedule`](crate::schedule::Schedule), /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system. - /// This allows for running systems in a push-based fashion. + /// /// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases /// due to its better performance and ability to run non-conflicting systems simultaneously. /// - /// If you want to prevent Commands from registering the same system multiple times, consider using [`Local`](crate::system::Local) + /// # Note + /// + /// If the same system is registered more than once, + /// each registration will be considered a different system, + /// and they will each be given their own [`SystemId`]. + /// + /// If you want to avoid registering the same system multiple times, + /// consider using [`Commands::run_system_cached`] or storing the [`SystemId`] + /// in a [`Local`](crate::system::Local). /// /// # Example /// /// ``` /// # use bevy_ecs::{prelude::*, world::CommandQueue, system::SystemId}; - /// /// #[derive(Resource)] /// struct Counter(i32); /// - /// fn register_system(mut local_system: Local>, mut commands: Commands) { + /// fn register_system( + /// mut commands: Commands, + /// mut local_system: Local>, + /// ) { /// if let Some(system) = *local_system { /// commands.run_system(system); /// } else { @@ -993,73 +963,122 @@ impl<'w, 's> Commands<'w, 's> { SystemId::from_entity(entity) } - /// Removes a system previously registered with [`Commands::register_system`] or [`World::register_system`]. + /// Removes a system previously registered with [`Commands::register_system`] + /// or [`World::register_system`]. /// - /// See [`World::unregister_system`] for more information. + /// After removing a system, the [`SystemId`] becomes invalid + /// and attempting to use it afterwards will result in an error. + /// Re-adding the removed system will register it with a new `SystemId`. + /// + /// # Fallible + /// + /// This command will fail if the given [`SystemId`] + /// does not correspond to a [`System`](crate::system::System). + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). pub fn unregister_system(&mut self, system_id: SystemId) where I: SystemInput + Send + 'static, O: Send + 'static, { - self.queue(command::unregister_system(system_id).handle_error_with(error_handler::warn())); + self.queue(command::unregister_system(system_id).handle_error_with(warn)); } - /// Removes a system previously registered with [`World::register_system_cached`]. + /// Removes a system previously registered with one of the following: + /// - [`Commands::run_system_cached`] + /// - [`World::run_system_cached`] + /// - [`World::register_system_cached`] /// - /// See [`World::unregister_system_cached`] for more information. - pub fn unregister_system_cached< + /// # Fallible + /// + /// This command will fail if the given system + /// is not currently cached in a [`CachedSystemId`](crate::system::CachedSystemId) resource. + /// + /// It will internally return a [`RegisteredSystemError`](crate::system::system_registry::RegisteredSystemError), + /// which will be handled by [logging the error at the `warn` level](warn). + pub fn unregister_system_cached(&mut self, system: S) + where I: SystemInput + Send + 'static, O: 'static, M: 'static, S: IntoSystem + Send + 'static, - >( - &mut self, - system: S, - ) { - self.queue( - command::unregister_system_cached(system).handle_error_with(error_handler::warn()), - ); + { + self.queue(command::unregister_system_cached(system).handle_error_with(warn)); } - /// Similar to [`Self::run_system`], but caching the [`SystemId`] in a - /// [`CachedSystemId`](crate::system::CachedSystemId) resource. + /// Runs a cached system, registering it if necessary. /// - /// See [`World::register_system_cached`] for more information. - pub fn run_system_cached + Send + 'static>( - &mut self, - system: S, - ) { - self.queue(command::run_system_cached(system).handle_error_with(error_handler::warn())); + /// Unlike [`Commands::run_system`], this method does not require manual registration. + /// + /// The first time this method is called for a particular system, + /// it will register the system and store its [`SystemId`] in a + /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. + /// + /// If you would rather manage the [`SystemId`] yourself, + /// or register multiple copies of the same system, + /// use [`Commands::register_system`] instead. + /// + /// # Limitations + /// + /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of + /// the same type must be equal. This means that closures that capture the environment, and + /// function pointers, are not accepted. + /// + /// If you want to access values from the environment within a system, + /// consider passing them in as inputs via [`Commands::run_system_cached_with`]. + /// + /// If that's not an option, consider [`Commands::register_system`] instead. + pub fn run_system_cached(&mut self, system: S) + where + M: 'static, + S: IntoSystem<(), (), M> + Send + 'static, + { + self.queue(command::run_system_cached(system).handle_error_with(warn)); } - /// Similar to [`Self::run_system_with`], but caching the [`SystemId`] in a - /// [`CachedSystemId`](crate::system::CachedSystemId) resource. + /// Runs a cached system with an input, registering it if necessary. /// - /// See [`World::register_system_cached`] for more information. + /// Unlike [`Commands::run_system_with`], this method does not require manual registration. + /// + /// The first time this method is called for a particular system, + /// it will register the system and store its [`SystemId`] in a + /// [`CachedSystemId`](crate::system::CachedSystemId) resource for later. + /// + /// If you would rather manage the [`SystemId`] yourself, + /// or register multiple copies of the same system, + /// use [`Commands::register_system`] instead. + /// + /// # Limitations + /// + /// This method only accepts ZST (zero-sized) systems to guarantee that any two systems of + /// the same type must be equal. This means that closures that capture the environment, and + /// function pointers, are not accepted. + /// + /// If you want to access values from the environment within a system, + /// consider passing them in as inputs. + /// + /// If that's not an option, consider [`Commands::register_system`] instead. pub fn run_system_cached_with(&mut self, system: S, input: I::Inner<'static>) where I: SystemInput: Send> + Send + 'static, M: 'static, S: IntoSystem + Send + 'static, { - self.queue( - command::run_system_cached_with(system, input).handle_error_with(error_handler::warn()), - ); + self.queue(command::run_system_cached_with(system, input).handle_error_with(warn)); } - /// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that - /// isn't scoped to specific targets. + /// Sends a "global" [`Trigger`](crate::observer::Trigger) without any targets. /// - /// [`Trigger`]: crate::observer::Trigger + /// This will run any [`Observer`] of the given [`Event`] that isn't scoped to specific targets. #[track_caller] pub fn trigger(&mut self, event: impl Event) { self.queue(command::trigger(event)); } - /// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that - /// watches those targets. + /// Sends a [`Trigger`](crate::observer::Trigger) for the given targets. /// - /// [`Trigger`]: crate::observer::Trigger + /// This will run any [`Observer`] of the given [`Event`] watching those targets. #[track_caller] pub fn trigger_targets( &mut self, @@ -1072,9 +1091,17 @@ impl<'w, 's> Commands<'w, 's> { /// Spawns an [`Observer`] and returns the [`EntityCommands`] associated /// with the entity that stores the observer. /// + /// `observer` can be any system whose first parameter is a [`Trigger`]. + /// /// **Calling [`observe`](EntityCommands::observe) on the returned /// [`EntityCommands`] will observe the observer itself, which you very /// likely do not want.** + /// + /// # Panics + /// + /// Panics if the given system is an exclusive system. + /// + /// [`Trigger`]: crate::observer::Trigger pub fn add_observer( &mut self, observer: impl IntoObserverSystem, @@ -1084,14 +1111,16 @@ impl<'w, 's> Commands<'w, 's> { /// Sends an arbitrary [`Event`]. /// - /// This is a convenience method for sending events without requiring an [`EventWriter`]. - /// ## Performance + /// This is a convenience method for sending events + /// without requiring an [`EventWriter`](crate::event::EventWriter). + /// + /// # Performance + /// /// Since this is a command, exclusive world access is used, which means that it will not profit from /// system-level parallelism on supported platforms. - /// If these events are performance-critical or very frequently - /// sent, consider using a typed [`EventWriter`] instead. /// - /// [`EventWriter`]: crate::event::EventWriter + /// If these events are performance-critical or very frequently sent, + /// consider using a typed [`EventWriter`](crate::event::EventWriter) instead. #[track_caller] pub fn send_event(&mut self, event: E) -> &mut Self { self.queue(command::send_event(event)); @@ -1102,17 +1131,21 @@ impl<'w, 's> Commands<'w, 's> { /// /// Calls [`World::try_run_schedule`](World::try_run_schedule). /// - /// This will log an error if the schedule is not available to be run. + /// # Fallible /// - /// # Examples + /// This command will fail if the given [`ScheduleLabel`] + /// does not correspond to a [`Schedule`](crate::schedule::Schedule). + /// + /// It will internally return a [`TryRunScheduleError`](crate::world::error::TryRunScheduleError), + /// which will be handled by [logging the error at the `warn` level](warn). + /// + /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ecs::schedule::ScheduleLabel; - /// # /// # #[derive(Default, Resource)] /// # struct Counter(u32); - /// # /// #[derive(ScheduleLabel, Hash, Debug, PartialEq, Eq, Clone, Copy)] /// struct FooSchedule; /// @@ -1138,7 +1171,7 @@ impl<'w, 's> Commands<'w, 's> { /// # assert_eq!(world.resource::().0, 1); /// ``` pub fn run_schedule(&mut self, label: impl ScheduleLabel) { - self.queue(command::run_schedule(label).handle_error_with(error_handler::warn())); + self.queue(command::run_schedule(label).handle_error_with(warn)); } } @@ -1146,35 +1179,36 @@ impl<'w, 's> Commands<'w, 's> { /// /// # Note /// -/// Most [`Commands`] (and thereby [`EntityCommands`]) are deferred: when you call the command, -/// if it requires mutable access to the [`World`] (that is, if it removes, adds, or changes something), -/// it's not executed immediately. Instead, the command is added to a "command queue." -/// The command queue is applied between [`Schedules`](crate::schedule::Schedule), one by one, -/// so that each command can have exclusive access to the World. +/// Most [`Commands`] (and thereby [`EntityCommands`]) are deferred: +/// when you call the command, if it requires mutable access to the [`World`] +/// (that is, if it removes, adds, or changes something), it's not executed immediately. +/// +/// Instead, the command is added to a "command queue." +/// The command queue is applied later +/// when the [`ApplyDeferred`](crate::schedule::ApplyDeferred) system runs. +/// Commands are executed one-by-one so that +/// each command can have exclusive access to the `World`. /// /// # Fallible /// -/// Due to their deferred nature, an entity you're trying to change with an [`EntityCommand`] can be -/// despawned by the time the command is executed. All deferred entity commands will check if the -/// entity exists at the time of execution and will return an error if it doesn't. +/// Due to their deferred nature, an entity you're trying to change with an [`EntityCommand`] +/// can be despawned by the time the command is executed. +/// +/// All deferred entity commands will check whether the entity exists at the time of execution +/// and will return an error if it doesn't. /// /// # Error handling /// /// An [`EntityCommand`] can return a [`Result`](crate::error::Result), -/// which will be passed to an error handler if the `Result` is an error. +/// which will be passed to an [error handler](crate::error) if the `Result` is an error. /// -/// Error handlers are functions/closures of the form `fn(&mut World, Error)`. -/// They are granted exclusive access to the [`World`], which enables them to -/// respond to the error in whatever way is necessary. -/// -/// The [default error handler](error_handler::default) panics. -/// It can be configured by enabling the `configurable_error_handler` cargo feature, -/// then setting the `GLOBAL_ERROR_HANDLER`. +/// The default error handler panics. It can be configured via +/// the [`DefaultErrorHandler`](crate::error::DefaultErrorHandler) resource. /// /// Alternatively, you can customize the error handler for a specific command /// by calling [`EntityCommands::queue_handled`]. /// -/// The [`error_handler`] module provides some simple error handlers for convenience. +/// The [`error`](crate::error) module provides some simple error handlers for convenience. pub struct EntityCommands<'a> { pub(crate) entity: Entity, pub(crate) commands: Commands<'a, 'a>, @@ -1200,6 +1234,7 @@ impl<'a> EntityCommands<'a> { } /// Returns an [`EntityCommands`] with a smaller lifetime. + /// /// This is useful if you have `&mut EntityCommands` but you need `EntityCommands`. pub fn reborrow(&mut self) -> EntityCommands { EntityCommands { @@ -1211,7 +1246,8 @@ impl<'a> EntityCommands<'a> { /// Get an [`EntityEntryCommands`] for the [`Component`] `T`, /// allowing you to modify it or insert it if it isn't already present. /// - /// See also [`insert_if_new`](Self::insert_if_new), which lets you insert a [`Bundle`] without overwriting it. + /// See also [`insert_if_new`](Self::insert_if_new), + /// which lets you insert a [`Bundle`] without overwriting it. /// /// # Example /// @@ -1222,15 +1258,32 @@ impl<'a> EntityCommands<'a> { /// #[derive(Component)] /// struct Level(u32); /// + /// + /// #[derive(Component, Default)] + /// struct Mana { + /// max: u32, + /// current: u32, + /// } + /// /// fn level_up_system(mut commands: Commands, player: Res) { + /// // If a component already exists then modify it, otherwise insert a default value /// commands /// .entity(player.entity) /// .entry::() - /// // Modify the component if it exists /// .and_modify(|mut lvl| lvl.0 += 1) - /// // Otherwise insert a default value /// .or_insert(Level(0)); + /// + /// // Add a default value if none exists, and then modify the existing or new value + /// commands + /// .entity(player.entity) + /// .entry::() + /// .or_default() + /// .and_modify(|mut mana| { + /// mana.max += 10; + /// mana.current = mana.max; + /// }); /// } + /// /// # bevy_ecs::system::assert_is_system(level_up_system); /// ``` pub fn entry(&mut self) -> EntityEntryCommands { @@ -1245,12 +1298,6 @@ impl<'a> EntityCommands<'a> { /// This will overwrite any previous value(s) of the same component type. /// See [`EntityCommands::insert_if_new`] to keep the old value instead. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert`] instead. - /// /// # Example /// /// ``` @@ -1297,15 +1344,10 @@ impl<'a> EntityCommands<'a> { self.queue(entity_command::insert(bundle, InsertMode::Replace)) } - /// Similar to [`Self::insert`] but will only insert if the predicate returns true. + /// Adds a [`Bundle`] of components to the entity if the predicate returns true. + /// /// This is useful for chaining method calls. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if`] instead. - /// /// # Example /// /// ``` @@ -1341,17 +1383,10 @@ impl<'a> EntityCommands<'a> { /// Adds a [`Bundle`] of components to the entity without overwriting. /// /// This is the same as [`EntityCommands::insert`], but in case of duplicate - /// components will leave the old values instead of replacing them with new - /// ones. + /// components will leave the old values instead of replacing them with new ones. /// /// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present, /// as well as initialize it with a default value. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] instead. #[track_caller] pub fn insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { self.queue(entity_command::insert(bundle, InsertMode::Keep)) @@ -1361,16 +1396,7 @@ impl<'a> EntityCommands<'a> { /// predicate returns true. /// /// This is the same as [`EntityCommands::insert_if`], but in case of duplicate - /// components will leave the old values instead of replacing them with new - /// ones. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not - /// exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] - /// instead. + /// components will leave the old values instead of replacing them with new ones. #[track_caller] pub fn insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where @@ -1383,15 +1409,11 @@ impl<'a> EntityCommands<'a> { } } - /// Adds a dynamic component to an entity. + /// Adds a dynamic [`Component`] to the entity. /// - /// See [`EntityWorldMut::insert_by_id`] for more information. + /// This will overwrite any previous value(s) of the same component type. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_by_id`] instead. + /// You should prefer to use the typed API [`EntityCommands::insert`] where possible. /// /// # Safety /// @@ -1411,9 +1433,16 @@ impl<'a> EntityCommands<'a> { ) } - /// Attempts to add a dynamic component to an entity. + /// Adds a dynamic [`Component`] to the entity. /// - /// See [`EntityWorldMut::insert_by_id`] for more information. + /// This will overwrite any previous value(s) of the same component type. + /// + /// You should prefer to use the typed API [`EntityCommands::try_insert`] where possible. + /// + /// # Note + /// + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. /// /// # Safety /// @@ -1430,17 +1459,18 @@ impl<'a> EntityCommands<'a> { // - `ComponentId` safety is ensured by the caller. // - `T` safety is ensured by the caller. unsafe { entity_command::insert_by_id(component_id, value, InsertMode::Replace) }, - error_handler::silent(), + ignore, ) } - /// Tries to add a [`Bundle`] of components to the entity. + /// Adds a [`Bundle`] of components to the entity. /// /// This will overwrite any previous value(s) of the same component type. /// /// # Note /// - /// Unlike [`Self::insert`], this will not panic if the associated entity does not exist. + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. /// /// # Example /// @@ -1462,60 +1492,36 @@ impl<'a> EntityCommands<'a> { /// } /// /// fn add_combat_stats_system(mut commands: Commands, player: Res) { - /// commands.entity(player.entity) - /// // You can try_insert individual components: - /// .try_insert(Defense(10)) + /// commands.entity(player.entity) + /// // You can insert individual components: + /// .try_insert(Defense(10)) + /// // You can also insert tuples of components: + /// .try_insert(CombatBundle { + /// health: Health(100), + /// strength: Strength(40), + /// }); /// - /// // You can also insert tuples of components: - /// .try_insert(CombatBundle { - /// health: Health(100), - /// strength: Strength(40), - /// }); + /// // Suppose this occurs in a parallel adjacent system or process. + /// commands.entity(player.entity).despawn(); /// - /// // Suppose this occurs in a parallel adjacent system or process - /// commands.entity(player.entity) - /// .despawn(); - /// - /// commands.entity(player.entity) - /// // This will not panic nor will it add the component - /// .try_insert(Defense(5)); + /// // This will not panic nor will it add the component. + /// commands.entity(player.entity).try_insert(Defense(5)); /// } /// # bevy_ecs::system::assert_is_system(add_combat_stats_system); /// ``` #[track_caller] pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue_handled( - entity_command::insert(bundle, InsertMode::Replace), - error_handler::silent(), - ) + self.queue_handled(entity_command::insert(bundle, InsertMode::Replace), ignore) } - /// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true. + /// Adds a [`Bundle`] of components to the entity if the predicate returns true. + /// /// This is useful for chaining method calls. /// - /// # Example + /// # Note /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource)] - /// # struct PlayerEntity { entity: Entity } - /// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } } - /// #[derive(Component)] - /// struct StillLoadingStats; - /// #[derive(Component)] - /// struct Health(u32); - /// - /// fn add_health_system(mut commands: Commands, player: Res) { - /// commands.entity(player.entity) - /// .try_insert_if(Health(10), || !player.is_spectator()) - /// .remove::(); - /// - /// commands.entity(player.entity) - /// // This will not panic nor will it add the component - /// .try_insert_if(Health(5), || !player.is_spectator()); - /// } - /// # bevy_ecs::system::assert_is_system(add_health_system); - /// ``` + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. #[track_caller] pub fn try_insert_if(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where @@ -1528,41 +1534,16 @@ impl<'a> EntityCommands<'a> { } } - /// Tries to add a [`Bundle`] of components to the entity without overwriting if the + /// Adds a [`Bundle`] of components to the entity without overwriting if the /// predicate returns true. /// /// This is the same as [`EntityCommands::try_insert_if`], but in case of duplicate - /// components will leave the old values instead of replacing them with new - /// ones. + /// components will leave the old values instead of replacing them with new ones. /// /// # Note /// - /// Unlike [`Self::insert_if_new_and`], this will not panic if the associated entity does - /// not exist. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Resource)] - /// # struct PlayerEntity { entity: Entity } - /// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } } - /// #[derive(Component)] - /// struct StillLoadingStats; - /// #[derive(Component)] - /// struct Health(u32); - /// - /// fn add_health_system(mut commands: Commands, player: Res) { - /// commands.entity(player.entity) - /// .try_insert_if(Health(10), || player.is_spectator()) - /// .remove::(); - /// - /// commands.entity(player.entity) - /// // This will not panic nor will it overwrite the component - /// .try_insert_if_new_and(Health(5), || player.is_spectator()); - /// } - /// # bevy_ecs::system::assert_is_system(add_health_system); - /// ``` + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. #[track_caller] pub fn try_insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where @@ -1575,30 +1556,31 @@ impl<'a> EntityCommands<'a> { } } - /// Tries to add a [`Bundle`] of components to the entity without overwriting. + /// Adds a [`Bundle`] of components to the entity without overwriting. /// /// This is the same as [`EntityCommands::try_insert`], but in case of duplicate - /// components will leave the old values instead of replacing them with new - /// ones. + /// components will leave the old values instead of replacing them with new ones. /// /// # Note /// - /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. #[track_caller] pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue_handled( - entity_command::insert(bundle, InsertMode::Keep), - error_handler::silent(), - ) + self.queue_handled(entity_command::insert(bundle, InsertMode::Keep), ignore) } /// Removes a [`Bundle`] of components from the entity. /// + /// This will remove all components that intersect with the provided bundle; + /// the entity does not need to have all the components in the bundle. + /// + /// This will emit a warning if the entity does not exist. + /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # /// # #[derive(Resource)] /// # struct PlayerEntity { entity: Entity } /// #[derive(Component)] @@ -1619,7 +1601,7 @@ impl<'a> EntityCommands<'a> { /// .entity(player.entity) /// // You can remove individual components: /// .remove::() - /// // You can also remove pre-defined Bundles of components: + /// // You can also remove pre-defined bundles of components: /// .remove::() /// // You can also remove tuples of components and bundles. /// // This is equivalent to the calls above: @@ -1628,24 +1610,79 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` #[track_caller] - pub fn remove(&mut self) -> &mut Self - where - T: Bundle, - { - self.queue_handled(entity_command::remove::(), error_handler::warn()) + pub fn remove(&mut self) -> &mut Self { + self.queue_handled(entity_command::remove::(), warn) } - /// Removes a [`Bundle`] of components from the entity. + /// Removes a [`Bundle`] of components from the entity if the predicate returns true. /// - /// # Note - /// - /// Unlike [`Self::remove`], this will not panic if the associated entity does not exist. + /// This is useful for chaining method calls. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct PlayerEntity { entity: Entity } + /// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } } + /// #[derive(Component)] + /// struct Health(u32); + /// #[derive(Component)] + /// struct Strength(u32); + /// #[derive(Component)] + /// struct Defense(u32); + /// + /// #[derive(Bundle)] + /// struct CombatBundle { + /// health: Health, + /// strength: Strength, + /// } + /// + /// fn remove_combat_stats_system(mut commands: Commands, player: Res) { + /// commands + /// .entity(player.entity) + /// .remove_if::<(Defense, CombatBundle)>(|| !player.is_spectator()); + /// } + /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); + /// ``` + #[track_caller] + pub fn remove_if(&mut self, condition: impl FnOnce() -> bool) -> &mut Self { + if condition() { + self.remove::() + } else { + self + } + } + + /// Removes a [`Bundle`] of components from the entity if the predicate returns true. + /// + /// This is useful for chaining method calls. + /// + /// # Note + /// + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. + #[track_caller] + pub fn try_remove_if(&mut self, condition: impl FnOnce() -> bool) -> &mut Self { + if condition() { + self.try_remove::() + } else { + self + } + } + + /// Removes a [`Bundle`] of components from the entity. + /// + /// This will remove all components that intersect with the provided bundle; + /// the entity does not need to have all the components in the bundle. + /// + /// Unlike [`Self::remove`], + /// this will not emit a warning if the entity does not exist. /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # /// # #[derive(Resource)] /// # struct PlayerEntity { entity: Entity } /// #[derive(Component)] @@ -1666,7 +1703,7 @@ impl<'a> EntityCommands<'a> { /// .entity(player.entity) /// // You can remove individual components: /// .try_remove::() - /// // You can also remove pre-defined Bundles of components: + /// // You can also remove pre-defined bundles of components: /// .try_remove::() /// // You can also remove tuples of components and bundles. /// // This is equivalent to the calls above: @@ -1674,40 +1711,40 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` - pub fn try_remove(&mut self) -> &mut Self - where - T: Bundle, - { - self.queue_handled(entity_command::remove::(), error_handler::silent()) + pub fn try_remove(&mut self) -> &mut Self { + self.queue_handled(entity_command::remove::(), ignore) } - /// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity. + /// Removes a [`Bundle`] of components from the entity, + /// and also removes any components required by the components in the bundle. + /// + /// This will remove all components that intersect with the provided bundle; + /// the entity does not need to have all the components in the bundle. /// /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; - /// + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct PlayerEntity { entity: Entity } + /// # /// #[derive(Component)] /// #[require(B)] /// struct A; /// #[derive(Component, Default)] /// struct B; /// - /// #[derive(Resource)] - /// struct PlayerEntity { entity: Entity } - /// /// fn remove_with_requires_system(mut commands: Commands, player: Res) { /// commands /// .entity(player.entity) - /// // Remove both A and B components from the entity, because B is required by A + /// // Removes both A and B from the entity, because B is required by A. /// .remove_with_requires::(); /// } /// # bevy_ecs::system::assert_is_system(remove_with_requires_system); /// ``` #[track_caller] - pub fn remove_with_requires(&mut self) -> &mut Self { - self.queue(entity_command::remove_with_requires::()) + pub fn remove_with_requires(&mut self) -> &mut Self { + self.queue(entity_command::remove_with_requires::()) } /// Removes a dynamic [`Component`] from the entity if it exists. @@ -1730,60 +1767,55 @@ impl<'a> EntityCommands<'a> { /// /// This will emit a warning if the entity does not exist. /// - /// See [`World::despawn`] for more details. - /// /// # Note /// - /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured - /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) + /// that is configured to despawn descendants. + /// + /// For example, this will recursively despawn [`Children`](crate::hierarchy::Children). /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # /// # #[derive(Resource)] /// # struct CharacterToRemove { entity: Entity } /// # /// fn remove_character_system( /// mut commands: Commands, /// character_to_remove: Res - /// ) - /// { + /// ) { /// commands.entity(character_to_remove.entity).despawn(); /// } /// # bevy_ecs::system::assert_is_system(remove_character_system); /// ``` #[track_caller] pub fn despawn(&mut self) { - self.queue_handled(entity_command::despawn(), error_handler::warn()); - } - /// Despawns the provided entity and its descendants. - #[deprecated( - since = "0.16.0", - note = "Use entity.despawn(), which now automatically despawns recursively." - )] - pub fn despawn_recursive(&mut self) { - self.despawn(); + self.queue_handled(entity_command::despawn(), warn); } /// Despawns the entity. /// - /// This will not emit a warning if the entity does not exist, essentially performing - /// the same function as [`Self::despawn`] without emitting warnings. + /// Unlike [`Self::despawn`], + /// this will not emit a warning if the entity does not exist. /// /// # Note /// - /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) that are configured - /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). + /// This will also despawn the entities in any [`RelationshipTarget`](crate::relationship::RelationshipTarget) + /// that is configured to despawn descendants. + /// + /// For example, this will recursively despawn [`Children`](crate::hierarchy::Children). pub fn try_despawn(&mut self) { - self.queue_handled(entity_command::despawn(), error_handler::silent()); + self.queue_handled(entity_command::despawn(), ignore); } - /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. + /// Pushes an [`EntityCommand`] to the queue, + /// which will get executed for the current [`Entity`]. /// - /// If the [`EntityCommand`] returns a [`Result`], - /// it will be handled using the [default error handler](error_handler::default). + /// The [default error handler](crate::error::DefaultErrorHandler) + /// will be used to handle error cases. + /// Every [`EntityCommand`] checks whether the entity exists at the time of execution + /// and returns an error if it does not. /// /// To use a custom error handler, see [`EntityCommands::queue_handled`]. /// @@ -1794,7 +1826,7 @@ impl<'a> EntityCommands<'a> { /// - [`(EntityWorldMut)`](EntityWorldMut) `->` [`Result`] /// - A built-in command from the [`entity_command`] module. /// - /// # Examples + /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; @@ -1816,10 +1848,12 @@ impl<'a> EntityCommands<'a> { self } - /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. + /// Pushes an [`EntityCommand`] to the queue, + /// which will get executed for the current [`Entity`]. /// - /// If the [`EntityCommand`] returns a [`Result`], - /// the given `error_handler` will be used to handle error cases. + /// The given `error_handler` will be used to handle error cases. + /// Every [`EntityCommand`] checks whether the entity exists at the time of execution + /// and returns an error if it does not. /// /// To implicitly use the default error handler, see [`EntityCommands::queue`]. /// @@ -1830,12 +1864,13 @@ impl<'a> EntityCommands<'a> { /// - [`(EntityWorldMut)`](EntityWorldMut) `->` [`Result`] /// - A built-in command from the [`entity_command`] module. /// - /// # Examples + /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::system::error_handler; /// # fn my_system(mut commands: Commands) { + /// use bevy_ecs::error::warn; + /// /// commands /// .spawn_empty() /// // Closures with this signature implement `EntityCommand`. @@ -1845,7 +1880,7 @@ impl<'a> EntityCommands<'a> { /// println!("Successfully parsed the value {} for entity {}", value, entity.id()); /// Ok(()) /// }, - /// error_handler::warn() + /// warn /// ); /// # } /// # bevy_ecs::system::assert_is_system(my_system); @@ -1853,7 +1888,7 @@ impl<'a> EntityCommands<'a> { pub fn queue_handled + CommandWithEntity, T, M>( &mut self, command: C, - error_handler: fn(&mut World, BevyError), + error_handler: fn(BevyError, ErrorContext), ) -> &mut Self { self.commands .queue_handled(command.with_entity(self.entity), error_handler); @@ -1862,13 +1897,10 @@ impl<'a> EntityCommands<'a> { /// Removes all components except the given [`Bundle`] from the entity. /// - /// This can also be used to remove all the components from the entity by passing it an empty Bundle. - /// /// # Example /// /// ``` /// # use bevy_ecs::prelude::*; - /// # /// # #[derive(Resource)] /// # struct PlayerEntity { entity: Entity } /// #[derive(Component)] @@ -1888,28 +1920,19 @@ impl<'a> EntityCommands<'a> { /// commands /// .entity(player.entity) /// // You can retain a pre-defined Bundle of components, - /// // with this removing only the Defense component + /// // with this removing only the Defense component. /// .retain::() - /// // You can also retain only a single component - /// .retain::() - /// // And you can remove all the components by passing in an empty Bundle - /// .retain::<()>(); + /// // You can also retain only a single component. + /// .retain::(); /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` #[track_caller] - pub fn retain(&mut self) -> &mut Self - where - T: Bundle, - { - self.queue(entity_command::retain::()) + pub fn retain(&mut self) -> &mut Self { + self.queue(entity_command::retain::()) } - /// Logs the components of the entity at the info level. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. + /// Logs the components of the entity at the [`info`](log::info) level. pub fn log_components(&mut self) -> &mut Self { self.queue(entity_command::log_components()) } @@ -1925,6 +1948,7 @@ impl<'a> EntityCommands<'a> { } /// Sends a [`Trigger`](crate::observer::Trigger) targeting the entity. + /// /// This will run any [`Observer`] of the given [`Event`] watching this entity. #[track_caller] pub fn trigger(&mut self, event: impl Event) -> &mut Self { @@ -1954,20 +1978,19 @@ impl<'a> EntityCommands<'a> { /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; - /// /// #[derive(Component, Clone)] /// struct ComponentA(u32); /// #[derive(Component, Clone)] /// struct ComponentB(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create an empty entity + /// // Create an empty entity. /// let target = commands.spawn_empty().id(); /// - /// // Create a new entity and keep its EntityCommands + /// // Create a new entity and keep its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Clone only ComponentA onto the target + /// // Clone only ComponentA onto the target. /// entity.clone_with(target, |builder| { /// builder.deny::(); /// }); @@ -2001,17 +2024,16 @@ impl<'a> EntityCommands<'a> { /// /// ``` /// # use bevy_ecs::prelude::*; - /// /// #[derive(Component, Clone)] /// struct ComponentA(u32); /// #[derive(Component, Clone)] /// struct ComponentB(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create a new entity and keep its EntityCommands + /// // Create a new entity and store its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Create a clone of the first entity + /// // Create a clone of the first entity. /// let mut entity_clone = entity.clone_and_spawn(); /// } /// # bevy_ecs::system::assert_is_system(example_system); @@ -2040,17 +2062,16 @@ impl<'a> EntityCommands<'a> { /// /// ``` /// # use bevy_ecs::prelude::*; - /// /// #[derive(Component, Clone)] /// struct ComponentA(u32); /// #[derive(Component, Clone)] /// struct ComponentB(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create a new entity and keep its EntityCommands + /// // Create a new entity and store its EntityCommands. /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// - /// // Create a clone of the first entity, but without ComponentB + /// // Create a clone of the first entity, but without ComponentB. /// let mut entity_clone = entity.clone_and_spawn_with(|builder| { /// builder.deny::(); /// }); @@ -2114,61 +2135,48 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { } impl<'a, T: Component> EntityEntryCommands<'a, T> { - /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. - /// - /// See also [`or_insert_with`](Self::or_insert_with). - /// - /// # Panics - /// - /// Panics if the entity does not exist. - /// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version. + /// [Insert](EntityCommands::insert) `default` into this entity, + /// if `T` is not already present. #[track_caller] pub fn or_insert(&mut self, default: T) -> &mut Self { self.entity_commands.insert_if_new(default); self } - /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. + /// [Insert](EntityCommands::insert) `default` into this entity, + /// if `T` is not already present. /// - /// Unlike [`or_insert`](Self::or_insert), this will not panic if the entity does not exist. + /// # Note /// - /// See also [`or_insert_with`](Self::or_insert_with). + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. #[track_caller] pub fn or_try_insert(&mut self, default: T) -> &mut Self { self.entity_commands.try_insert_if_new(default); self } - /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. - /// - /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). - /// - /// # Panics - /// - /// Panics if the entity does not exist. - /// See [`or_try_insert_with`](Self::or_try_insert_with) for a non-panicking version. + /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, + /// if `T` is not already present. #[track_caller] pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { self.or_insert(default()) } - /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. + /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, + /// if `T` is not already present. /// - /// Unlike [`or_insert_with`](Self::or_insert_with), this will not panic if the entity does not exist. + /// # Note /// - /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). + /// If the entity does not exist when this command is executed, + /// the resulting error will be ignored. #[track_caller] pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { self.or_try_insert(default()) } - /// [Insert](EntityCommands::insert) `T::default` into this entity, if `T` is not already present. - /// - /// See also [`or_insert`](Self::or_insert) and [`or_from_world`](Self::or_from_world). - /// - /// # Panics - /// - /// Panics if the entity does not exist. + /// [Insert](EntityCommands::insert) `T::default` into this entity, + /// if `T` is not already present. #[track_caller] pub fn or_default(&mut self) -> &mut Self where @@ -2177,13 +2185,8 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { self.or_insert(T::default()) } - /// [Insert](EntityCommands::insert) `T::from_world` into this entity, if `T` is not already present. - /// - /// See also [`or_insert`](Self::or_insert) and [`or_default`](Self::or_default). - /// - /// # Panics - /// - /// Panics if the entity does not exist. + /// [Insert](EntityCommands::insert) `T::from_world` into this entity, + /// if `T` is not already present. #[track_caller] pub fn or_from_world(&mut self) -> &mut Self where @@ -2211,13 +2214,13 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// commands /// .entity(player.entity) /// .entry::() - /// // Modify the component if it exists + /// // Modify the component if it exists. /// .and_modify(|mut lvl| lvl.0 += 1) - /// // Otherwise insert a default value + /// // Otherwise, insert a default value. /// .or_insert(Level(0)) - /// // Return the EntityCommands for the entity + /// // Return the EntityCommands for the entity. /// .entity() - /// // And continue chaining method calls + /// // Continue chaining method calls. /// .insert(Name::new("Player")); /// } /// # bevy_ecs::system::assert_is_system(level_up_system); @@ -2614,4 +2617,17 @@ mod tests { assert!(world.contains_resource::>()); assert!(world.contains_resource::>()); } + + #[test] + fn track_spawn_ticks() { + let mut world = World::default(); + world.increment_change_tick(); + let expected = world.change_tick(); + let id = world.commands().spawn_empty().id(); + world.flush(); + assert_eq!( + Some(expected), + world.entities().entity_get_spawned_or_despawned_at(id) + ); + } } diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 2f471c13c5..bee491017d 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -20,9 +20,13 @@ struct ParallelCommandQueue { /// [`Bundle`](crate::prelude::Bundle) type need to be spawned, consider using /// [`Commands::spawn_batch`] for better performance. /// -/// Note: Because command application order will depend on how many threads are ran, non-commutative commands may result in non-deterministic results. +/// # Note +/// +/// Because command application order will depend on how many threads are ran, +/// non-commutative commands may result in non-deterministic results. +/// +/// # Example /// -/// Example: /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_tasks::ComputeTaskPool; diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index f3072dfdb7..8277fcb0f9 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -1,7 +1,6 @@ use crate::{ - archetype::ArchetypeComponentId, component::{ComponentId, Tick}, - query::Access, + query::{Access, FilteredAccessSet}, schedule::{InternedSystemSet, SystemSet}, system::{ check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem, @@ -14,6 +13,8 @@ use alloc::{borrow::Cow, vec, vec::Vec}; use core::marker::PhantomData; use variadics_please::all_tuples; +use super::SystemParamValidationError; + /// A function system that runs with exclusive [`World`] access. /// /// You get this by calling [`IntoSystem::into_system`] on a function that only accepts @@ -85,8 +86,8 @@ where } #[inline] - fn archetype_component_access(&self) -> &Access { - &self.system_meta.archetype_component_access + fn component_access_set(&self) -> &FilteredAccessSet { + &self.system_meta.component_access_set } #[inline] @@ -110,18 +111,12 @@ where #[inline] unsafe fn run_unsafe( - &mut self, - _input: SystemIn<'_, Self>, - _world: UnsafeWorldCell, - ) -> Self::Out { - panic!("Cannot run exclusive systems with a shared World reference"); - } - - fn run_without_applying_deferred( &mut self, input: SystemIn<'_, Self>, - world: &mut World, + world: UnsafeWorldCell, ) -> Self::Out { + // SAFETY: The safety is upheld by the caller. + let world = unsafe { world.world_mut() }; world.last_change_tick_scope(self.system_meta.last_run, |world| { #[cfg(feature = "trace")] let _span_guard = self.system_meta.system_span.enter(); @@ -154,9 +149,12 @@ where } #[inline] - unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> bool { + unsafe fn validate_param_unsafe( + &mut self, + _world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { // All exclusive system params are always available. - true + Ok(()) } #[inline] @@ -165,8 +163,6 @@ where self.param_state = Some(F::Param::init(world, &mut self.system_meta)); } - fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {} - #[inline] fn check_change_tick(&mut self, change_tick: Tick) { check_system_change_tick( @@ -285,6 +281,7 @@ macro_rules! impl_exclusive_system_function { // without using this function. It fails to recognize that `func` // is a function, potentially because of the multiple impls of `FnMut` fn call_inner( + _: PhantomData, mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out, input: In::Inner<'_>, world: &mut World, @@ -293,7 +290,7 @@ macro_rules! impl_exclusive_system_function { f(In::wrap(input), world, $($param,)*) } let ($($param,)*) = param_value; - call_inner(self, input, world, $($param),*) + call_inner(PhantomData::, self, input, world, $($param),*) } } }; diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index f271e32e2f..f87182ab1f 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -4,7 +4,7 @@ use crate::{ system::{Local, SystemMeta, SystemParam, SystemState}, world::World, }; -use bevy_utils::synccell::SyncCell; +use bevy_platform::cell::SyncCell; use core::marker::PhantomData; use variadics_please::all_tuples; diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 0f3950d1d4..49bcf1cc78 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,5 +1,4 @@ use crate::{ - archetype::{ArchetypeComponentId, ArchetypeGeneration}, component::{ComponentId, Tick}, prelude::FromWorld, query::{Access, FilteredAccessSet}, @@ -18,7 +17,7 @@ use variadics_please::all_tuples; #[cfg(feature = "trace")] use tracing::{info_span, Span}; -use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder}; +use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError}; /// The metadata of a [`System`]. #[derive(Clone)] @@ -28,22 +27,11 @@ pub struct SystemMeta { /// - soundness issues (e.g. multiple [`SystemParam`]s mutably accessing the same component) /// - ambiguities in the schedule (e.g. two systems that have some sort of conflicting access) pub(crate) component_access_set: FilteredAccessSet, - /// This [`Access`] is used to determine which systems can run in parallel with each other - /// in the multithreaded executor. - /// - /// We use a [`ArchetypeComponentId`] as it is more precise than just checking [`ComponentId`]: - /// for example if you have one system with `Query<&mut T, With>` and one system with `Query<&mut T, With>` - /// they conflict if you just look at the [`ComponentId`] of `T`; but if there are no archetypes with - /// both `A`, `B` and `T` then in practice there's no risk of conflict. By using [`ArchetypeComponentId`] - /// we can be more precise because we can check if the existing archetypes of the [`World`] - /// cause a conflict - pub(crate) archetype_component_access: Access, // NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent // SystemParams from overriding each other is_send: bool, has_deferred: bool, pub(crate) last_run: Tick, - param_warn_policy: ParamWarnPolicy, #[cfg(feature = "trace")] pub(crate) system_span: Span, #[cfg(feature = "trace")] @@ -55,12 +43,10 @@ impl SystemMeta { let name = core::any::type_name::(); Self { name: name.into(), - archetype_component_access: Access::default(), component_access_set: FilteredAccessSet::default(), is_send: true, has_deferred: false, last_run: Tick::new(0), - param_warn_policy: ParamWarnPolicy::Panic, #[cfg(feature = "trace")] system_span: info_span!("system", name = name), #[cfg(feature = "trace")] @@ -116,58 +102,6 @@ impl SystemMeta { self.has_deferred = true; } - /// Changes the warn policy. - #[inline] - pub(crate) fn set_param_warn_policy(&mut self, warn_policy: ParamWarnPolicy) { - self.param_warn_policy = warn_policy; - } - - /// Advances the warn policy after validation failed. - #[inline] - pub(crate) fn advance_param_warn_policy(&mut self) { - self.param_warn_policy.advance(); - } - - /// Emits a warning about inaccessible system param if policy allows it. - #[inline] - pub fn try_warn_param