diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7769bc92b831..b0704a2395528 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -244,7 +244,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.29.5 + uses: crate-ci/typos@v1.29.7 - name: Typos info if: failure() run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 17ac22019ebf0..8a04fadc94530 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -59,7 +59,7 @@ jobs: env: # needs to be in sync with [package.metadata.docs.rs] RUSTFLAGS: --cfg docsrs_dep - RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs --generate-link-to-definition + RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs --generate-link-to-definition --html-after-content docs-rs/trait-tags.html run: | cargo doc \ -Zunstable-options \ diff --git a/.github/workflows/validation-jobs.yml b/.github/workflows/validation-jobs.yml index 6f4291b0aa07a..464d77502830c 100644 --- a/.github/workflows/validation-jobs.yml +++ b/.github/workflows/validation-jobs.yml @@ -161,7 +161,7 @@ jobs: example_name=`basename $example .ron` echo -n $example_name > last_example_run echo "running $example_name - "`date` - time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome" + time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "statically-linked-dxc,bevy_ci_testing,trace,trace_chrome" sleep 10 if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then mkdir screenshots-$example_name @@ -186,6 +186,17 @@ jobs: name: example-run-windows path: example-run/ + compare-windows-screenshots: + name: Compare Windows screenshots + needs: [run-examples-on-windows-dx12] + uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml + with: + commit: ${{ github.sha }} + branch: ${{ github.ref_name }} + artifact: screenshots-windows + os: windows + secrets: inherit + run-examples-on-wasm: if: ${{ github.event_name == 'merge_group' }} runs-on: ubuntu-latest @@ -208,13 +219,6 @@ jobs: target/ key: ${{ runner.os }}-wasm-run-examples-${{ hashFiles('**/Cargo.toml') }} - - name: install xvfb, llvmpipe and lavapipe - run: | - sudo apt-get update -y -qq - sudo add-apt-repository ppa:kisak/turtle -y - sudo apt-get update - sudo apt install -y xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - - name: Install wasm-bindgen run: cargo install --force wasm-bindgen-cli diff --git a/.gitignore b/.gitignore index d887b4e70797f..0d39edea49083 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ crates/**/target benches/**/target tools/**/target **/*.rs.bk +rustc-ice-*.txt # DX12 wgpu backend dxcompiler.dll diff --git a/CREDITS.md b/CREDITS.md index cc8b15083ac69..c0375bd38edac 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -20,8 +20,8 @@ * Cake from [Kenney's Food Kit](https://www.kenney.nl/assets/food-kit) (CC0 1.0 Universal) * Ground tile from [Kenney's Tower Defense Kit](https://www.kenney.nl/assets/tower-defense-kit) (CC0 1.0 Universal) * Game icons from [Kenney's Game Icons](https://www.kenney.nl/assets/game-icons) (CC0 1.0 Universal) -* Space ships from [Kenny's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal) -* UI borders from [Kenny's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal) +* Space ships from [Kenney's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal) +* UI borders from [Kenney's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal) * glTF animated fox from [glTF Sample Models][fox] * Low poly fox [by PixelMannen] (CC0 1.0 Universal) * Rigging and animation [by @tomkranis on Sketchfab] ([CC-BY 4.0]) @@ -32,7 +32,7 @@ * Epic orchestra music sample, modified to loop, from [Migfus20](https://freesound.org/people/Migfus20/sounds/560449/) ([CC BY 4.0 DEED](https://creativecommons.org/licenses/by/4.0/)) [MorphStressTest]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest -[fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox +[fox]: https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/Fox [by PixelMannen]: https://opengameart.org/content/fox-and-shiba [by @tomkranis on Sketchfab]: https://sketchfab.com/models/371dea88d7e04a76af5763f2a36866bc [CC-BY 4.0]: https://creativecommons.org/licenses/by/4.0/ diff --git a/Cargo.toml b/Cargo.toml index 8c2543542b6ae..c00d1519098f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,10 @@ members = [ # Bevy's error codes. This is a crate so we can automatically check all of the code blocks. "errors", ] +exclude = [ + # Integration tests are not part of the workspace + "tests-integration", +] [workspace.lints.clippy] doc_markdown = "warn" @@ -42,6 +46,7 @@ undocumented_unsafe_blocks = "warn" unwrap_or_default = "warn" needless_lifetimes = "allow" too_many_arguments = "allow" +nonstandard_macro_braces = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" @@ -87,6 +92,7 @@ undocumented_unsafe_blocks = "warn" unwrap_or_default = "warn" needless_lifetimes = "allow" too_many_arguments = "allow" +nonstandard_macro_braces = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" @@ -256,6 +262,9 @@ bevy_remote = ["bevy_internal/bevy_remote"] # Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation) spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"] +# Statically linked DXC shader compiler for DirectX 12 +statically-linked-dxc = ["bevy_internal/statically-linked-dxc"] + # Tracing support, saving a file in Chrome Tracing format trace_chrome = ["trace", "bevy_internal/trace_chrome"] @@ -270,7 +279,7 @@ trace_tracy_memory = [ ] # Tracing support -trace = ["bevy_internal/trace"] +trace = ["bevy_internal/trace", "dep:tracing"] # Basis Universal compressed texture support basis-universal = ["bevy_internal/basis-universal"] @@ -457,9 +466,6 @@ meshlet = ["bevy_internal/meshlet"] # Enables processing meshes into meshlet meshes for bevy_pbr meshlet_processor = ["bevy_internal/meshlet_processor"] -# Enable support for the ios_simulator by downgrading some rendering capabilities -ios_simulator = ["bevy_internal/ios_simulator"] - # Enable built in global state machines bevy_state = ["bevy_internal/bevy_state"] @@ -469,6 +475,9 @@ track_location = ["bevy_internal/track_location"] # Enable function reflection reflect_functions = ["bevy_internal/reflect_functions"] +# Enable documentation reflection +reflect_documentation = ["bevy_internal/reflect_documentation"] + # Enable winit custom cursor support custom_cursor = ["bevy_internal/custom_cursor"] @@ -477,6 +486,7 @@ ghost_nodes = ["bevy_internal/ghost_nodes"] [dependencies] bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false } +tracing = { version = "0.1", default-features = false, optional = true } # Wasm does not support dynamic linking. [target.'cfg(not(target_family = "wasm"))'.dependencies] @@ -491,6 +501,13 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" 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. +bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.0-dev", default-features = false } +bevy_state = { path = "crates/bevy_state", version = "0.16.0-dev", default-features = false } +bevy_asset = { path = "crates/bevy_asset", version = "0.16.0-dev", default-features = false } +bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.0-dev", default-features = false } +bevy_image = { path = "crates/bevy_image", version = "0.16.0-dev", default-features = false } +bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.0-dev", default-features = false } # Needed to poll Task examples futures-lite = "2.0.1" async-std = "1.13" @@ -503,6 +520,7 @@ http-body-util = "0.1" anyhow = "1" macro_rules_attribute = "0.2" accesskit = "0.17" +nonmax = "0.5" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] smol = "2" @@ -920,6 +938,17 @@ description = "Showcases different blend modes" category = "3D Rendering" wasm = true +[[example]] +name = "edit_material_on_gltf" +path = "examples/3d/edit_material_on_gltf.rs" +doc-scrape-examples = true + +[package.metadata.example.edit_material_on_gltf] +name = "Edit Gltf Material" +description = "Showcases changing materials of a Gltf after Scene spawn" +category = "3D Rendering" +wasm = true + [[example]] name = "lighting" path = "examples/3d/lighting.rs" @@ -1996,6 +2025,17 @@ description = "Demonstrates how to send and receive events of the same type in a category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "entity_disabling" +path = "examples/ecs/entity_disabling.rs" +doc-scrape-examples = true + +[package.metadata.example.entity_disabling] +name = "Entity disabling" +description = "Demonstrates how to hide entities from the ECS without deleting them" +category = "ECS (Entity Component System)" +wasm = true + [[example]] name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" @@ -2121,6 +2161,7 @@ wasm = false name = "fallible_systems" path = "examples/ecs/fallible_systems.rs" doc-scrape-examples = true +required-features = ["bevy_mesh_picking_backend"] [package.metadata.example.fallible_systems] name = "Fallible Systems" @@ -2691,6 +2732,18 @@ description = "A shader that renders a mesh multiple times in one draw call usin category = "Shaders" wasm = true +[[example]] +name = "custom_render_phase" +path = "examples/shader/custom_render_phase.rs" +doc-scrape-examples = true + +[package.metadata.example.custom_render_phase] +name = "Custom Render Phase" +description = "Shows how to make a complete render phase" +category = "Shaders" +wasm = true + + [[example]] name = "automatic_instancing" path = "examples/shader/automatic_instancing.rs" @@ -3952,7 +4005,6 @@ name = "Sprite Picking" description = "Demonstrates picking sprites and sprite atlases" category = "Picking" wasm = true -required-features = ["bevy_sprite_picking_backend"] [[example]] name = "debug_picking" @@ -4052,7 +4104,15 @@ panic = "abort" # for details on why this is needed. Since dependencies don't expect to be built # with `--cfg docsrs` (and thus fail to compile) we use a different cfg. rustc-args = ["--cfg", "docsrs_dep"] -rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +rustdoc-args = [ + "-Zunstable-options", + "--generate-link-to-definition", + # Embed tags to the top of documentation pages for common Bevy traits + # that are implemented by the current type, like `Component` or `Resource`. + # This makes it easier to see at a glance what types are used for. + "--html-after-content", + "docs-rs/trait-tags.html", +] all-features = true cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] diff --git a/assets/models/animated/Fox.glb b/assets/models/animated/Fox.glb index 2bb946e2d4815..1ef5c0d05658c 100644 Binary files a/assets/models/animated/Fox.glb and b/assets/models/animated/Fox.glb differ diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index 813deb251e0b1..e768a7b149c41 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -7,10 +7,7 @@ entities: { 4294967296: ( components: { - "bevy_ecs::name::Name": ( - hash: 17588334858059901562, - name: "joe", - ), + "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)), "bevy_transform::components::transform::Transform": ( translation: (0.0, 0.0, 0.0), diff --git a/assets/shaders/automatic_instancing.wgsl b/assets/shaders/automatic_instancing.wgsl new file mode 100644 index 0000000000000..35276246b094e --- /dev/null +++ b/assets/shaders/automatic_instancing.wgsl @@ -0,0 +1,43 @@ +#import bevy_pbr::{ + mesh_functions, + view_transformations::position_world_to_clip +} + +@group(2) @binding(0) var texture: texture_2d; +@group(2) @binding(1) var texture_sampler: sampler; + +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, + @location(1) color: vec4, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + + // Lookup the tag for the given mesh + let tag = mesh_functions::get_tag(vertex.instance_index); + var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); + out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); + out.clip_position = position_world_to_clip(out.world_position.xyz); + + let tex_dim = textureDimensions(texture); + // Find the texel coordinate as derived from the tag + let texel_coord = vec2(tag % tex_dim.x, tag / tex_dim.x); + + out.color = textureLoad(texture, texel_coord, 0); + return out; +} + +@fragment +fn fragment( + mesh: VertexOutput, +) -> @location(0) vec4 { + return mesh.color; +} \ No newline at end of file diff --git a/assets/shaders/bindless_material.wgsl b/assets/shaders/bindless_material.wgsl index 9d9d068d4c069..3c91dcebafdbd 100644 --- a/assets/shaders/bindless_material.wgsl +++ b/assets/shaders/bindless_material.wgsl @@ -1,14 +1,22 @@ #import bevy_pbr::forward_io::VertexOutput #import bevy_pbr::mesh_bindings::mesh +#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d} struct Color { base_color: vec4, } +// This structure is a mapping from bindless index to the index in the +// appropriate slab +struct MaterialBindings { + material: u32, // 0 + color_texture: u32, // 1 + color_texture_sampler: u32, // 2 +} + #ifdef BINDLESS -@group(2) @binding(0) var material_color: binding_array; -@group(2) @binding(1) var material_color_texture: binding_array, 4>; -@group(2) @binding(2) var material_color_sampler: binding_array; +@group(2) @binding(0) var materials: binding_array; +@group(2) @binding(10) var material_color: binding_array; #else // BINDLESS @group(2) @binding(0) var material_color: Color; @group(2) @binding(1) var material_color_texture: texture_2d; @@ -19,15 +27,15 @@ struct Color { fn fragment(in: VertexOutput) -> @location(0) vec4 { #ifdef BINDLESS let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; - let base_color = material_color[slot].base_color; + let base_color = material_color[materials[slot].material].base_color; #else // BINDLESS let base_color = material_color.base_color; #endif // BINDLESS return base_color * textureSampleLevel( #ifdef BINDLESS - material_color_texture[slot], - material_color_sampler[slot], + bindless_textures_2d[materials[slot].color_texture], + bindless_samplers_filtering[materials[slot].color_texture_sampler], #else // BINDLESS material_color_texture, material_color_sampler, diff --git a/assets/shaders/custom_stencil.wgsl b/assets/shaders/custom_stencil.wgsl new file mode 100644 index 0000000000000..6f2fa2da4f977 --- /dev/null +++ b/assets/shaders/custom_stencil.wgsl @@ -0,0 +1,41 @@ +//! A shader showing how to use the vertex position data to output the +//! stencil in the right position + +// First we import everything we need from bevy_pbr +// A 2d shader would be vevry similar but import from bevy_sprite instead +#import bevy_pbr::{ + mesh_functions, + view_transformations::position_world_to_clip +} + +struct Vertex { + // This is needed if you are using batching and/or gpu preprocessing + // It's a built in so you don't need to define it in the vertex layout + @builtin(instance_index) instance_index: u32, + // Like we defined for the vertex layout + // position is at location 0 + @location(0) position: vec3, +}; + +// This is the output of the vertex shader and we also use it as the input for the fragment shader +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec4, +}; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + // This is how bevy computes the world position + // The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing + var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); + out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); + out.clip_position = position_world_to_clip(out.world_position.xyz); + return out; +} + +@fragment +fn fragment(in: VertexOutput) -> @location(0) vec4 { + // Output a red color to represent the stencil of the mesh + return vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/assets/shaders/custom_ui_material.wgsl b/assets/shaders/custom_ui_material.wgsl index 815fceb5fc748..528fa55302fab 100644 --- a/assets/shaders/custom_ui_material.wgsl +++ b/assets/shaders/custom_ui_material.wgsl @@ -2,7 +2,7 @@ #import bevy_ui::ui_vertex_output::UiVertexOutput @group(1) @binding(0) var color: vec4; -@group(1) @binding(1) var slider: f32; +@group(1) @binding(1) var slider: vec4; @group(1) @binding(2) var material_color_texture: texture_2d; @group(1) @binding(3) var material_color_sampler: sampler; @group(1) @binding(4) var border_color: vec4; @@ -50,7 +50,7 @@ fn fragment(in: UiVertexOutput) -> @location(0) vec4 { // sample the texture at this position if it's to the left of the slider value // otherwise return a fully transparent color - if in.uv.x < slider { + if in.uv.x < slider.x { let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color; return output_color; } else { diff --git a/assets/shaders/storage_buffer.wgsl b/assets/shaders/storage_buffer.wgsl index 1859e8dde2755..c27053b9a21b3 100644 --- a/assets/shaders/storage_buffer.wgsl +++ b/assets/shaders/storage_buffer.wgsl @@ -4,7 +4,6 @@ } @group(2) @binding(0) var colors: array, 5>; -@group(2) @binding(1) var color_id: u32; struct Vertex { @builtin(instance_index) instance_index: u32, @@ -20,11 +19,12 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; + let tag = mesh_functions::get_tag(vertex.instance_index); var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0)); out.clip_position = position_world_to_clip(out.world_position.xyz); - out.color = colors[color_id]; + out.color = colors[tag]; return out; } diff --git a/benches/Cargo.toml b/benches/Cargo.toml index d4b032eab0339..4fc852d1a2a61 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -50,6 +50,7 @@ undocumented_unsafe_blocks = "warn" unwrap_or_default = "warn" needless_lifetimes = "allow" too_many_arguments = "allow" +nonstandard_macro_braces = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" diff --git a/benches/benches/bevy_ecs/change_detection.rs b/benches/benches/bevy_ecs/change_detection.rs index 8d49c2b8c7e1c..84a8d4c39e64a 100644 --- a/benches/benches/bevy_ecs/change_detection.rs +++ b/benches/benches/bevy_ecs/change_detection.rs @@ -267,7 +267,7 @@ fn none_changed_detection(criterion: &mut Criterion) { } } fn insert_if_bit_enabled(entity: &mut EntityWorldMut, i: u16) { - if i & 1 << B != 0 { + if i & (1 << B) != 0 { entity.insert(Data::(1.0)); } } diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index b11c5b2b7576e..2908332ea5e5f 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -22,7 +22,7 @@ fn setup(system_count: usize) -> (World, Schedule) { } fn insert_if_bit_enabled(entity: &mut EntityWorldMut, i: u16) { - if i & 1 << B != 0 { + if i & (1 << B) != 0 { entity.insert(A::(1.0)); } } diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index 91c2b5427a068..e5e7639066fe8 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -105,49 +105,49 @@ fn add_archetypes(world: &mut World, count: u16) { e.insert(A::<10>(1.0)); e.insert(A::<11>(1.0)); e.insert(A::<12>(1.0)); - if i & 1 << 1 != 0 { + if i & (1 << 1) != 0 { e.insert(A::<13>(1.0)); } - if i & 1 << 2 != 0 { + if i & (1 << 2) != 0 { e.insert(A::<14>(1.0)); } - if i & 1 << 3 != 0 { + if i & (1 << 3) != 0 { e.insert(A::<15>(1.0)); } - if i & 1 << 4 != 0 { + if i & (1 << 4) != 0 { e.insert(A::<16>(1.0)); } - if i & 1 << 5 != 0 { + if i & (1 << 5) != 0 { e.insert(A::<18>(1.0)); } - if i & 1 << 6 != 0 { + if i & (1 << 6) != 0 { e.insert(A::<19>(1.0)); } - if i & 1 << 7 != 0 { + if i & (1 << 7) != 0 { e.insert(A::<20>(1.0)); } - if i & 1 << 8 != 0 { + if i & (1 << 8) != 0 { e.insert(A::<21>(1.0)); } - if i & 1 << 9 != 0 { + if i & (1 << 9) != 0 { e.insert(A::<22>(1.0)); } - if i & 1 << 10 != 0 { + if i & (1 << 10) != 0 { e.insert(A::<23>(1.0)); } - if i & 1 << 11 != 0 { + if i & (1 << 11) != 0 { e.insert(A::<24>(1.0)); } - if i & 1 << 12 != 0 { + if i & (1 << 12) != 0 { e.insert(A::<25>(1.0)); } - if i & 1 << 13 != 0 { + if i & (1 << 13) != 0 { e.insert(A::<26>(1.0)); } - if i & 1 << 14 != 0 { + if i & (1 << 14) != 0 { e.insert(A::<27>(1.0)); } - if i & 1 << 15 != 0 { + if i & (1 << 15) != 0 { e.insert(A::<28>(1.0)); } } diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 92687cf67892a..7c474cc4f8aaf 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -2,7 +2,8 @@ use core::hint::black_box; use benches::bench; use bevy_ecs::bundle::Bundle; -use bevy_ecs::component::ComponentCloneHandler; +use bevy_ecs::component::ComponentCloneBehavior; +use bevy_ecs::entity::EntityCloner; use bevy_ecs::hierarchy::ChildOf; use bevy_ecs::reflect::AppTypeRegistry; use bevy_ecs::{component::Component, world::World}; @@ -52,7 +53,10 @@ type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); /// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to /// use the [`Reflect`] trait instead of [`Clone`]. -fn set_reflect_clone_handler(world: &mut World) { +fn reflection_cloner( + world: &mut World, + recursive: bool, +) -> EntityCloner { // Get mutable access to the type registry, creating it if it does not exist yet. let registry = world.get_resource_or_init::(); @@ -67,12 +71,15 @@ fn set_reflect_clone_handler(world: &mut World) // this bundle are saved. let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); - let clone_handlers = world.get_component_clone_handlers_mut(); + let mut builder = EntityCloner::build(world); // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`. for component in component_ids { - clone_handlers.set_component_handler(component, ComponentCloneHandler::reflect_handler()); + builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect()); } + builder.recursive(recursive); + + builder.finish() } /// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a @@ -91,18 +98,18 @@ fn bench_clone( ) { let mut world = World::default(); - if clone_via_reflect { - set_reflect_clone_handler::(&mut world); - } + let mut cloner = if clone_via_reflect { + reflection_cloner::(&mut world, false) + } else { + EntityCloner::default() + }; // Spawn the first entity, which will be cloned in the benchmark routine. let id = world.spawn(B::default()).id(); b.iter(|| { - // Queue the command to clone the entity. - world.commands().entity(black_box(id)).clone_and_spawn(); - - // Run the command. + // clones the given entity + cloner.spawn_clone(&mut world, black_box(id)); world.flush(); }); } @@ -125,9 +132,15 @@ fn bench_clone_hierarchy( ) { let mut world = World::default(); - if clone_via_reflect { - set_reflect_clone_handler::(&mut world); - } + let mut cloner = if clone_via_reflect { + reflection_cloner::(&mut world, true) + } else { + let mut builder = EntityCloner::build(&mut world); + builder.recursive(true); + builder.finish() + }; + + // Make the clone command recursive, so children are cloned as well. // Spawn the first entity, which will be cloned in the benchmark routine. let id = world.spawn(B::default()).id(); @@ -148,18 +161,8 @@ fn bench_clone_hierarchy( } } - // Flush all `set_parent()` commands. - world.flush(); - b.iter(|| { - world - .commands() - .entity(black_box(id)) - .clone_and_spawn_with(|builder| { - // Make the clone command recursive, so children are cloned as well. - builder.recursive(true); - }); - + cloner.spawn_clone(&mut world, black_box(id)); world.flush(); }); } diff --git a/benches/benches/bevy_ecs/iteration/par_iter_simple.rs b/benches/benches/bevy_ecs/iteration/par_iter_simple.rs index dfd3f9dfdab0d..971598005ab1b 100644 --- a/benches/benches/bevy_ecs/iteration/par_iter_simple.rs +++ b/benches/benches/bevy_ecs/iteration/par_iter_simple.rs @@ -19,7 +19,7 @@ struct Data(f32); pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); fn insert_if_bit_enabled(entity: &mut EntityWorldMut, i: u16) { - if i & 1 << B != 0 { + if i & (1 << B) != 0 { entity.insert(Data::(1.0)); } } diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index fcb9b0116bb95..283b984186150 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -1,7 +1,7 @@ use core::hint::black_box; use bevy_ecs::{ - bundle::Bundle, + bundle::{Bundle, NoBundleEffect}, component::Component, entity::Entity, system::{Query, SystemState}, @@ -36,7 +36,7 @@ fn setup(entity_count: u32) -> World { black_box(world) } -fn setup_wide(entity_count: u32) -> World { +fn setup_wide + Default>(entity_count: u32) -> World { let mut world = World::default(); world.spawn_batch((0..entity_count).map(|_| T::default())); black_box(world) diff --git a/clippy.toml b/clippy.toml index 26b39b4e841e8..2c98e8ed02c30 100644 --- a/clippy.toml +++ b/clippy.toml @@ -43,3 +43,6 @@ disallowed-methods = [ { 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 `{}`. +standard-macro-braces = [{ name = "children", brace = "[" }] diff --git a/crates/bevy_a11y/LICENSE-APACHE b/crates/bevy_a11y/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_a11y/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_a11y/LICENSE-MIT b/crates/bevy_a11y/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_a11y/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_animation/LICENSE-APACHE b/crates/bevy_animation/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_animation/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_animation/LICENSE-MIT b/crates/bevy_animation/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_animation/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_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 2bfd54d847d84..8663ea3f3f970 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -32,13 +32,8 @@ use crate::{ }; use bevy_app::{Animation, App, Plugin, PostUpdate}; -use bevy_asset::{Asset, AssetApp, Assets}; -use bevy_ecs::{ - entity::{VisitEntities, VisitEntitiesMut}, - prelude::*, - reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut}, - world::EntityMutExcept, -}; +use bevy_asset::{Asset, AssetApp, AssetEvents, Assets}; +use bevy_ecs::{prelude::*, world::EntityMutExcept}; use bevy_math::FloatOrd; use bevy_platform_support::{collections::HashMap, hash::NoOpHash}; use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; @@ -207,16 +202,16 @@ impl Hash for AnimationTargetId { /// Note that each entity can only be animated by one animation player at a /// time. However, you can change [`AnimationTarget`]'s `player` property at /// runtime to change which player is responsible for animating the entity. -#[derive(Clone, Copy, Component, Reflect, VisitEntities, VisitEntitiesMut)] -#[reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut)] +#[derive(Clone, Copy, Component, Reflect)] +#[reflect(Component)] pub struct AnimationTarget { /// The ID of this animation target. /// /// Typically, this is derived from the path. - #[visit_entities(ignore)] pub id: AnimationTargetId, /// The entity containing the [`AnimationPlayer`]. + #[entities] pub player: Entity, } @@ -1244,7 +1239,7 @@ impl Plugin for AnimationPlugin { .add_systems( PostUpdate, ( - graph::thread_animation_graphs, + graph::thread_animation_graphs.before(AssetEvents), advance_transitions, advance_animations, // TODO: `animate_targets` can animate anything, so diff --git a/crates/bevy_app/LICENSE-APACHE b/crates/bevy_app/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_app/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_app/LICENSE-MIT b/crates/bevy_app/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_app/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_app/src/app.rs b/crates/bevy_app/src/app.rs index 11602ff5ad719..63b91b5df4ae6 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -13,13 +13,13 @@ use bevy_ecs::{ event::{event_update_system, EventCursor}, intern::Interned, prelude::*, + result::{Error, SystemErrorContext}, schedule::{ScheduleBuildSettings, ScheduleLabel}, system::{IntoObserverSystem, SystemId, SystemInput}, }; use bevy_platform_support::collections::HashMap; use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe}; use log::debug; -use thiserror::Error; #[cfg(feature = "trace")] use tracing::info_span; @@ -44,7 +44,7 @@ pub use bevy_ecs::label::DynEq; /// A shorthand for `Interned`. pub type InternedAppLabel = Interned; -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub(crate) enum AppError { #[error("duplicate plugin {plugin_name:?}")] DuplicatePlugin { plugin_name: String }, @@ -1034,6 +1034,17 @@ impl App { .try_register_required_components_with::(constructor) } + /// Registers a component type as "disabling", + /// using [default query filters](bevy_ecs::entity_disabling::DefaultQueryFilters) to exclude entities with the component from queries. + /// + /// # Warning + /// + /// As discussed in the [module docs](bevy_ecs::entity_disabling), this can have performance implications, + /// as well as create interoperability issues, and should be used with caution. + pub fn register_disabling_component(&mut self) { + self.world_mut().register_disabling_component::(); + } + /// Returns a reference to the main [`SubApp`]'s [`World`]. This is the same as calling /// [`app.main().world()`]. /// @@ -1263,6 +1274,18 @@ impl App { self } + /// Set the global system error handler to use for systems that return a [`Result`]. + /// + /// See the [`bevy_ecs::result` module-level documentation](../../bevy_ecs/result/index.html) + /// for more information. + pub fn set_system_error_handler( + &mut self, + error_handler: fn(Error, 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. @@ -1394,7 +1417,6 @@ impl AppExit { } impl From for AppExit { - #[must_use] fn from(value: u8) -> Self { Self::from_code(value) } @@ -1532,7 +1554,6 @@ mod tests { #[test] fn test_derive_app_label() { use super::AppLabel; - use crate::{self as bevy_app}; #[derive(AppLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct UnitLabel; @@ -1664,7 +1685,6 @@ mod tests { #[test] fn test_extract_sees_changes() { use super::AppLabel; - use crate::{self as bevy_app}; #[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)] struct MySubApp; @@ -1693,9 +1713,9 @@ mod tests { fn raise_exits(mut exits: EventWriter) { // Exit codes chosen by a fair dice roll. // Unlikely to overlap with default values. - exits.send(AppExit::Success); - exits.send(AppExit::from_code(4)); - exits.send(AppExit::from_code(73)); + exits.write(AppExit::Success); + exits.write(AppExit::from_code(4)); + exits.write(AppExit::from_code(73)); } let exit = App::new().add_systems(Update, raise_exits).run(); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 13021924d033e..489b5415e30a3 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -20,6 +20,9 @@ extern crate std; extern crate alloc; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_app; + mod app; mod main_schedule; mod panic_handler; diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 2e3f48f2ca1e6..ead7c468c37a9 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -3,6 +3,7 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use bevy_ecs::{ event::EventRegistry, prelude::*, + result::{DefaultSystemErrorHandler, SystemErrorContext}, schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel}, system::{SystemId, SystemInput}, }; @@ -335,6 +336,22 @@ impl SubApp { self } + /// Set the global error handler to use for systems that return a [`Result`]. + /// + /// See the [`bevy_ecs::result` module-level documentation](../../bevy_ecs/result/index.html) + /// for more information. + pub fn set_system_error_handler( + &mut self, + error_handler: fn(Error, 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/terminal_ctrl_c_handler.rs b/crates/bevy_app/src/terminal_ctrl_c_handler.rs index 0eb34ccdbe98e..48af3c09f18a5 100644 --- a/crates/bevy_app/src/terminal_ctrl_c_handler.rs +++ b/crates/bevy_app/src/terminal_ctrl_c_handler.rs @@ -50,7 +50,7 @@ impl TerminalCtrlCHandlerPlugin { /// Sends a [`AppExit`] event when the user presses `Ctrl+C` on the terminal. pub fn exit_on_flag(mut events: EventWriter) { if SHOULD_EXIT.load(Ordering::Relaxed) { - events.send(AppExit::from_code(130)); + events.write(AppExit::from_code(130)); } } } diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index bea637067af91..7022eb4d5ab2a 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -22,6 +22,7 @@ trace = [] bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } 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 = [ "uuid", ] } @@ -67,10 +68,7 @@ js-sys = "0.3" uuid = { version = "1.13.1", default-features = false, features = ["js"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -notify-debouncer-full = { version = "0.4.0", optional = true } - -[dev-dependencies] -bevy_log = { path = "../bevy_log", version = "0.16.0-dev" } +notify-debouncer-full = { version = "0.5.0", optional = true } [lints] workspace = true diff --git a/crates/bevy_asset/LICENSE-APACHE b/crates/bevy_asset/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_asset/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_asset/LICENSE-MIT b/crates/bevy_asset/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_asset/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_asset/macros/LICENSE-APACHE b/crates/bevy_asset/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_asset/macros/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_asset/macros/LICENSE-MIT b/crates/bevy_asset/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_asset/macros/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_asset/src/asset_changed.rs b/crates/bevy_asset/src/asset_changed.rs index 04439d9c5ee36..5fc959eada8bd 100644 --- a/crates/bevy_asset/src/asset_changed.rs +++ b/crates/bevy_asset/src/asset_changed.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ archetype::Archetype, component::{ComponentId, Tick}, prelude::{Entity, Resource, World}, - query::{FilteredAccess, QueryFilter, QueryItem, ReadFetch, WorldQuery}, + query::{FilteredAccess, QueryData, QueryFilter, ReadFetch, WorldQuery}, storage::{Table, TableRow}, world::unsafe_world_cell::UnsafeWorldCell, }; @@ -151,13 +151,10 @@ pub struct AssetChangedState { #[expect(unsafe_code, reason = "WorldQuery is an unsafe trait.")] /// SAFETY: `ROQueryFetch` is the same as `QueryFetch` unsafe impl WorldQuery for AssetChanged { - type Item<'w> = (); type Fetch<'w> = AssetChangedFetch<'w, A>; type State = AssetChangedState; - fn shrink<'wlong: 'wshort, 'wshort>(_: QueryItem<'wlong, Self>) -> QueryItem<'wshort, Self> {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -228,8 +225,6 @@ unsafe impl WorldQuery for AssetChanged { } } - unsafe fn fetch<'w>(_: &mut Self::Fetch<'w>, _: Entity, _: TableRow) -> Self::Item<'w> {} - #[inline] fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { <&A>::update_component_access(&state.asset_id, access); @@ -287,13 +282,13 @@ unsafe impl QueryFilter for AssetChanged { #[cfg(test)] mod tests { - use crate::{self as bevy_asset, AssetEvents, AssetPlugin, Handle}; + use crate::{AssetEvents, AssetPlugin, Handle}; use alloc::{vec, vec::Vec}; use core::num::NonZero; use std::println; use crate::{AssetApp, Assets}; - use bevy_app::{App, AppExit, Last, Startup, TaskPoolPlugin, Update}; + use bevy_app::{App, AppExit, PostUpdate, Startup, TaskPoolPlugin, Update}; use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::{ component::Component, @@ -335,7 +330,7 @@ mod tests { _query: Query<&mut MyComponent, AssetChanged>, mut exit: EventWriter, ) { - exit.send(AppExit::Error(NonZero::::MIN)); + exit.write(AppExit::Error(NonZero::::MIN)); } run_app(compatible_filter); } @@ -410,7 +405,7 @@ mod tests { .init_asset::() .insert_resource(Counter(vec![0, 0, 0, 0])) .add_systems(Update, add_some) - .add_systems(Last, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEvents)); // First run of the app, `add_systems(Startup…)` runs. app.update(); // run_count == 0 @@ -445,7 +440,7 @@ mod tests { }, ) .add_systems(Update, update_some) - .add_systems(Last, count_update.after(AssetEvents)); + .add_systems(PostUpdate, count_update.after(AssetEvents)); // 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 e183b228cfa8a..739b6b087e778 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,8 +1,5 @@ use crate::asset_changed::AssetChanges; -use crate::{ - self as bevy_asset, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, - UntypedHandle, -}; +use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle}; use alloc::{sync::Arc, vec::Vec}; use bevy_ecs::{ prelude::EventWriter, @@ -98,6 +95,7 @@ impl AssetIndexAllocator { /// [`AssetPath`]: crate::AssetPath #[derive(Asset, TypePath)] pub struct LoadedUntypedAsset { + /// The handle to the loaded asset. #[dependency] pub handle: UntypedHandle, } @@ -283,6 +281,8 @@ impl DenseAssetStorage { /// at compile time. /// /// This tracks (and queues) [`AssetEvent`] events whenever changes to the collection occur. +/// To check whether the asset used by a given component has changed (due to a change in the handle or the underlying asset) +/// use the [`AssetChanged`](crate::asset_changed::AssetChanged) query filter. #[derive(Resource)] pub struct Assets { dense_storage: DenseAssetStorage, @@ -579,7 +579,7 @@ impl Assets { }; } } - events.send_batch(assets.queued_events.drain(..)); + events.write_batch(assets.queued_events.drain(..)); } /// A run condition for [`asset_events`]. The system will not run if there are no events to @@ -632,6 +632,7 @@ impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> { } } +/// An error returned when an [`AssetIndex`] has an invalid generation. #[derive(Error, Debug)] #[error("AssetIndex {index:?} has an invalid generation. The current generation is: '{current_generation}'.")] pub struct InvalidGenerationError { diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs index bfa7fa17b29c0..792d523a30063 100644 --- a/crates/bevy_asset/src/direct_access_ext.rs +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -5,6 +5,7 @@ use bevy_ecs::world::World; use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle}; +/// An extension trait for methods for working with assets directly from a [`World`]. pub trait DirectAssetAccessExt { /// Insert an asset similarly to [`Assets::add`]. fn add_asset(&mut self, asset: impl Into) -> Handle; diff --git a/crates/bevy_asset/src/event.rs b/crates/bevy_asset/src/event.rs index 832cc212d4b01..087cb44b5a138 100644 --- a/crates/bevy_asset/src/event.rs +++ b/crates/bevy_asset/src/event.rs @@ -8,6 +8,7 @@ use core::fmt::Debug; /// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`]. #[derive(Event, Clone, Debug)] pub struct AssetLoadFailedEvent { + /// The stable identifier of the asset that failed to load. pub id: AssetId, /// The asset path that was attempted. pub path: AssetPath<'static>, @@ -25,6 +26,7 @@ impl AssetLoadFailedEvent { /// An untyped version of [`AssetLoadFailedEvent`]. #[derive(Event, Clone, Debug)] pub struct UntypedAssetLoadFailedEvent { + /// The stable identifier of the asset that failed to load. pub id: UntypedAssetId, /// The asset path that was attempted. pub path: AssetPath<'static>, @@ -43,6 +45,7 @@ impl From<&AssetLoadFailedEvent> for UntypedAssetLoadFailedEvent { } /// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events. +#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")] #[derive(Event, Reflect)] pub enum AssetEvent { /// Emitted whenever an [`Asset`] is added. diff --git a/crates/bevy_asset/src/folder.rs b/crates/bevy_asset/src/folder.rs index 58e7ef9a9d716..c591c88688789 100644 --- a/crates/bevy_asset/src/folder.rs +++ b/crates/bevy_asset/src/folder.rs @@ -1,14 +1,16 @@ use alloc::vec::Vec; -use crate as bevy_asset; use crate::{Asset, UntypedHandle}; use bevy_reflect::TypePath; /// A "loaded folder" containing handles for all assets stored in a given [`AssetPath`]. /// +/// This is produced by [`AssetServer::load_folder`](crate::prelude::AssetServer::load_folder). +/// /// [`AssetPath`]: crate::AssetPath #[derive(Asset, TypePath)] pub struct LoadedFolder { + /// The handles of all assets stored in the folder. #[dependency] pub handles: Vec, } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 690d5061bd7a7..f2401c951b5a2 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -113,14 +113,21 @@ impl core::fmt::Debug for StrongHandle { } } -/// A strong or weak handle to a specific [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept +/// A handle to a specific [`Asset`] of type `A`. Handles act as abstract "references" to +/// assets, whose data are stored in the [`Assets`](crate::prelude::Assets) resource, +/// avoiding the need to store multiple copies of the same data. +/// +/// If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept /// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`], /// nor will it keep assets alive. /// +/// Modifying a *handle* will change which existing asset is referenced, but modifying the *asset* +/// (by mutating the [`Assets`](crate::prelude::Assets) resource) will change the asset for all handles referencing it. +/// /// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances /// of the [`Handle`] are dropped. /// -/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists). +/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists). #[derive(Reflect)] #[reflect(Default, Debug, Hash, PartialEq)] pub enum Handle { @@ -143,6 +150,7 @@ 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), @@ -283,7 +291,9 @@ impl From<&mut Handle> for UntypedAssetId { /// See [`Handle`] for more information. #[derive(Clone)] pub enum UntypedHandle { + /// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped. Strong(Arc), + /// A weak handle, which does not keep the referenced [`Asset`] alive. Weak(UntypedAssetId), } @@ -501,6 +511,24 @@ impl TryFrom for Handle { } } +/// Creates a weak [`Handle`] from a string literal containing a UUID. +/// +/// # Examples +/// +/// ``` +/// # use bevy_asset::{Handle, weak_handle}; +/// # type Shader = (); +/// const SHADER: Handle = weak_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); +/// ``` +#[macro_export] +macro_rules! weak_handle { + ($uuid:expr) => {{ + $crate::Handle::Weak($crate::AssetId::Uuid { + uuid: $crate::uuid::uuid!($uuid), + }) + }}; +} + /// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`]. #[derive(Error, Debug, PartialEq, Clone)] #[non_exhaustive] @@ -509,7 +537,12 @@ pub enum UntypedAssetConversionError { #[error( "This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>" )] - TypeIdMismatch { expected: TypeId, found: TypeId }, + TypeIdMismatch { + /// The expected [`TypeId`] of the [`Handle`] being converted to. + expected: TypeId, + /// The [`TypeId`] of the [`UntypedHandle`] being converted from. + found: TypeId, + }, } #[cfg(test)] diff --git a/crates/bevy_asset/src/id.rs b/crates/bevy_asset/src/id.rs index a1fe13615ee8b..4caf546163a75 100644 --- a/crates/bevy_asset/src/id.rs +++ b/crates/bevy_asset/src/id.rs @@ -26,7 +26,9 @@ pub enum AssetId { /// /// [`Assets`]: crate::Assets Index { + /// The unstable, opaque index of the asset. index: AssetIndex, + /// A marker to store the type information of the asset. #[reflect(ignore)] marker: PhantomData A>, }, @@ -34,7 +36,10 @@ pub enum AssetId { /// with one. /// /// [`Assets`]: crate::Assets - Uuid { uuid: Uuid }, + Uuid { + /// The UUID provided during asset registration. + uuid: Uuid, + }, } impl AssetId { @@ -165,12 +170,22 @@ pub enum UntypedAssetId { /// explicitly registered that way. /// /// [`Assets`]: crate::Assets - Index { type_id: TypeId, index: AssetIndex }, + Index { + /// An identifier that records the underlying asset type. + type_id: TypeId, + /// The unstable, opaque index of the asset. + index: AssetIndex, + }, /// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`] /// with one. /// /// [`Assets`]: crate::Assets - Uuid { type_id: TypeId, uuid: Uuid }, + Uuid { + /// An identifier that records the underlying asset type. + type_id: TypeId, + /// The UUID provided during asset registration. + uuid: Uuid, + }, } impl UntypedAssetId { @@ -404,7 +419,12 @@ impl TryFrom for AssetId { pub enum UntypedAssetIdConversionError { /// Caused when trying to convert an [`UntypedAssetId`] into an [`AssetId`] of the wrong type. #[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")] - TypeIdMismatch { expected: TypeId, found: TypeId }, + TypeIdMismatch { + /// The [`TypeId`] of the asset that we are trying to convert to. + expected: TypeId, + /// The [`TypeId`] of the asset that we are trying to convert from. + found: TypeId, + }, } #[cfg(test)] diff --git a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs index 25dd55ff48c8d..89ac9b3826a8f 100644 --- a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs +++ b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs @@ -24,6 +24,7 @@ pub struct EmbeddedWatcher { } impl EmbeddedWatcher { + /// Creates a new `EmbeddedWatcher` that watches for changes to the embedded assets in the given `dir`. pub fn new( dir: Dir, root_paths: Arc, PathBuf>>>, diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index cba775aaa6ddf..fce5149462247 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -15,6 +15,8 @@ use std::path::{Path, PathBuf}; #[cfg(feature = "embedded_watcher")] use alloc::borrow::ToOwned; +/// The name of the `embedded` [`AssetSource`], +/// as stored in the [`AssetSourceBuilders`] resource. pub const EMBEDDED: &str = "embedded"; /// A [`Resource`] that manages "rust source files" in a virtual in memory [`Dir`], which is intended @@ -77,6 +79,7 @@ impl EmbeddedAssetRegistry { self.dir.remove_asset(full_path) } + /// Registers the [`EMBEDDED`] [`AssetSource`] with the given [`AssetSourceBuilders`]. pub fn register_source(&self, sources: &mut AssetSourceBuilders) { let dir = self.dir.clone(); let processed_dir = self.dir.clone(); diff --git a/crates/bevy_asset/src/io/file/file_watcher.rs b/crates/bevy_asset/src/io/file/file_watcher.rs index c8700dcdd5941..64d3e711c975a 100644 --- a/crates/bevy_asset/src/io/file/file_watcher.rs +++ b/crates/bevy_asset/src/io/file/file_watcher.rs @@ -18,7 +18,9 @@ use std::path::{Path, PathBuf}; use tracing::error; /// An [`AssetWatcher`] that watches the filesystem for changes to asset files in a given root folder and emits [`AssetSourceEvent`] -/// for each relevant change. This uses [`notify_debouncer_full`] to retrieve "debounced" filesystem events. +/// for each relevant change. +/// +/// This uses [`notify_debouncer_full`] to retrieve "debounced" filesystem events. /// "Debouncing" defines a time window to hold on to events and then removes duplicate events that fall into this window. /// This introduces a small delay in processing events, but it helps reduce event duplicates. A small delay is also necessary /// on some systems to avoid processing a change event before it has actually been applied. @@ -27,6 +29,7 @@ pub struct FileWatcher { } impl FileWatcher { + /// Creates a new [`FileWatcher`] that watches for changes to the asset files in the given `path`. pub fn new( path: PathBuf, sender: Sender, diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index 719b424e6da78..96c43072e8f47 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -65,15 +65,14 @@ impl FileAssetReader { } } +/// A writer for the local filesystem. pub struct FileAssetWriter { root_path: PathBuf, } impl FileAssetWriter { - /// Creates a new `FileAssetIo` at a path relative to the executable's directory, optionally + /// Creates a new [`FileAssetWriter`] at a path relative to the executable's directory, optionally /// watching for changes. - /// - /// See `get_base_path` below. pub fn new + core::fmt::Debug>(path: P, create_root: bool) -> Self { let root_path = get_base_path().join(path.as_ref()); if create_root { diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index 37bdc306b7cc0..58c5006a8047e 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -132,8 +132,11 @@ impl<'a> PartialEq for AssetSourceId<'a> { /// and whether or not the source is processed. #[derive(Default)] pub struct AssetSourceBuilder { + /// The [`ErasedAssetReader`] to use on the unprocessed asset. pub reader: Option Box + Send + Sync>>, + /// The [`ErasedAssetWriter`] to use on the unprocessed asset. pub writer: Option Option> + Send + Sync>>, + /// The [`AssetWatcher`] to use for unprocessed assets, if any. pub watcher: Option< Box< dyn FnMut(crossbeam_channel::Sender) -> Option> @@ -141,9 +144,12 @@ pub struct AssetSourceBuilder { + Sync, >, >, + /// The [`ErasedAssetReader`] to use for processed assets. pub processed_reader: Option Box + Send + Sync>>, + /// The [`ErasedAssetWriter`] to use for processed assets. pub processed_writer: Option Option> + Send + Sync>>, + /// The [`AssetWatcher`] to use for processed assets, if any. pub processed_watcher: Option< Box< dyn FnMut(crossbeam_channel::Sender) -> Option> @@ -151,7 +157,9 @@ pub struct AssetSourceBuilder { + Sync, >, >, + /// The warning message to display when watching an unprocessed asset fails. pub watch_warning: Option<&'static str>, + /// The warning message to display when watching a processed asset fails. pub processed_watch_warning: Option<&'static str>, } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 85fe7a87ed132..f0b8e92a9320d 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -149,6 +149,9 @@ extern crate alloc; extern crate std; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_asset; + pub mod io; pub mod meta; pub mod processor; @@ -202,6 +205,7 @@ pub use server::*; /// Rusty Object Notation, a crate used to serialize and deserialize bevy assets. pub use ron; +pub use uuid; use crate::{ io::{embedded::EmbeddedAssetRegistry, AssetSourceBuilder, AssetSourceBuilders, AssetSourceId}, @@ -212,7 +216,7 @@ use alloc::{ sync::Arc, vec::Vec, }; -use bevy_app::{App, Last, Plugin, PreUpdate}; +use bevy_app::{App, Plugin, PostUpdate, PreUpdate}; use bevy_ecs::prelude::Component; use bevy_ecs::{ reflect::AppTypeRegistry, @@ -580,7 +584,7 @@ impl AssetApp for App { .add_event::>() .register_type::>() .add_systems( - Last, + PostUpdate, Assets::::asset_events .run_if(Assets::::asset_events_condition) .in_set(AssetEvents), @@ -626,7 +630,6 @@ pub struct AssetEvents; #[cfg(test)] mod tests { use crate::{ - self as bevy_asset, folder::LoadedFolder, handle::Handle, io::{ @@ -722,7 +725,7 @@ mod tests { .map_err(|_| Self::Error::CannotLoadDependency { dependency: dep.into(), })?; - let cool = loaded.get(); + let cool = loaded.get_asset().get(); embedded.push_str(&cool.text); } Ok(CoolText { diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 75192159a857f..3be672e0c194d 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -13,6 +13,7 @@ use alloc::{ }; use atomicow::CowArc; use bevy_ecs::world::World; +use bevy_log::warn; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use core::any::{Any, TypeId}; @@ -60,7 +61,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 +92,7 @@ where mut load_context: LoadContext<'a>, ) -> BoxedFuture< 'a, - Result>, + Result>, > { Box::pin(async move { let settings = meta @@ -152,7 +153,6 @@ 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 { @@ -166,7 +166,6 @@ impl LoadedAsset { value, dependencies, loader_dependencies: HashMap::default(), - labeled_assets: HashMap::default(), } } @@ -179,19 +178,6 @@ 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 { @@ -205,7 +191,6 @@ 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 { @@ -214,7 +199,6 @@ impl From> for ErasedLoadedAsset { value: Box::new(asset.value), dependencies: asset.dependencies, loader_dependencies: asset.loader_dependencies, - labeled_assets: asset.labeled_assets, } } } @@ -241,19 +225,6 @@ 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> { @@ -262,7 +233,6 @@ impl ErasedLoadedAsset { value: *value, dependencies: self.dependencies, loader_dependencies: self.loader_dependencies, - labeled_assets: self.labeled_assets, }), Err(value) => { self.value = value; @@ -290,6 +260,100 @@ 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]. /// @@ -397,8 +461,8 @@ impl<'a> LoadContext<'a> { ) -> Handle { let mut context = self.begin_labeled_asset(); let asset = load(&mut context); - let loaded_asset = context.finish(asset); - self.add_loaded_labeled_asset(label, loaded_asset) + let complete_asset = context.finish(asset); + self.add_loaded_labeled_asset(label, complete_asset) } /// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label. @@ -423,10 +487,14 @@ impl<'a> LoadContext<'a> { pub fn add_loaded_labeled_asset( &mut self, label: impl Into>, - loaded_asset: LoadedAsset, + loaded_asset: CompleteLoadedAsset, ) -> Handle { let label = label.into(); - let loaded_asset: ErasedLoadedAsset = loaded_asset.into(); + let CompleteLoadedAsset { + asset, + labeled_assets, + } = loaded_asset; + let loaded_asset: ErasedLoadedAsset = asset.into(); let labeled_path = self.asset_path.clone().with_label(label.clone()); let handle = self .asset_server @@ -438,6 +506,11 @@ impl<'a> LoadContext<'a> { handle: handle.clone().untyped(), }, ); + for (label, asset) in labeled_assets { + if self.labeled_assets.insert(label.clone(), asset).is_some() { + warn!("A labeled asset with the label \"{label}\" already exists. Replacing with the new asset."); + } + } handle } @@ -450,11 +523,13 @@ impl<'a> LoadContext<'a> { } /// "Finishes" this context by populating the final [`Asset`] value. - pub fn finish(self, value: A) -> LoadedAsset { - LoadedAsset { - value, - dependencies: self.dependencies, - loader_dependencies: self.loader_dependencies, + pub fn finish(self, value: A) -> CompleteLoadedAsset { + CompleteLoadedAsset { + asset: LoadedAsset { + value, + dependencies: self.dependencies, + loader_dependencies: self.loader_dependencies, + }, labeled_assets: self.labeled_assets, } } @@ -525,8 +600,8 @@ impl<'a> LoadContext<'a> { meta: &dyn AssetMetaDyn, loader: &dyn ErasedAssetLoader, reader: &mut dyn Reader, - ) -> Result { - let loaded_asset = self + ) -> Result { + let complete_asset = self .asset_server .load_with_meta_loader_and_reader( &path, @@ -544,7 +619,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(loaded_asset) + Ok(complete_asset) } /// Create a builder for loading nested assets in this context. diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 3d75027f5da3c..630ab1f92cd18 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, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext, - LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, + Asset, AssetLoadError, AssetPath, CompleteErasedLoadedAsset, CompleteLoadedAsset, + ErasedAssetLoader, Handle, LoadContext, LoadDirectError, 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 [`ErasedLoadedAsset`] (via [`Immediate`]). +/// or a [`CompleteErasedLoadedAsset`] (via [`Immediate`]). /// /// - in [`UnknownTyped`]: loading either a type-erased version of the asset -/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset -/// ([`LoadedUntypedAsset`]). +/// ([`CompleteErasedLoadedAsset`]), 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. @@ -386,7 +386,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> self, path: &AssetPath<'static>, asset_type_id: Option, - ) -> Result<(Arc, ErasedLoadedAsset), LoadDirectError> { + ) -> Result<(Arc, CompleteErasedLoadedAsset), LoadDirectError> { let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader { let loader = if let Some(asset_type_id) = asset_type_id { self.load_context @@ -448,7 +448,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 @@ -476,7 +476,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) @@ -492,7 +492,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/meta.rs b/crates/bevy_asset/src/meta.rs index 5217d193b2edb..0e972261198cc 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -5,8 +5,8 @@ use alloc::{ }; use crate::{ - self as bevy_asset, loader::AssetLoader, processor::Process, Asset, AssetPath, - DeserializeMetaError, VisitAssetDependencies, + loader::AssetLoader, processor::Process, Asset, AssetPath, DeserializeMetaError, + VisitAssetDependencies, }; use downcast_rs::{impl_downcast, Downcast}; use ron::ser::PrettyConfig; diff --git a/crates/bevy_asset/src/processor/log.rs b/crates/bevy_asset/src/processor/log.rs index 533a87d830a04..aefc3a96c631e 100644 --- a/crates/bevy_asset/src/processor/log.rs +++ b/crates/bevy_asset/src/processor/log.rs @@ -32,8 +32,10 @@ pub struct ProcessorTransactionLog { /// An error that occurs when reading from the [`ProcessorTransactionLog`] fails. #[derive(Error, Debug)] pub enum ReadLogError { + /// An invalid log line was encountered, consisting of the contained string. #[error("Encountered an invalid log line: '{0}'")] InvalidLine(String), + /// A file-system-based error occurred while reading the log file. #[error("Failed to read log file: {0}")] Io(#[from] futures_io::Error), } @@ -51,10 +53,13 @@ pub struct WriteLogError { /// An error that occurs when validating the [`ProcessorTransactionLog`] fails. #[derive(Error, Debug)] pub enum ValidateLogError { + /// An error that could not be recovered from. All assets will be reprocessed. #[error("Encountered an unrecoverable error. All assets will be reprocessed.")] UnrecoverableError, + /// A [`ReadLogError`]. #[error(transparent)] ReadLogError(#[from] ReadLogError), + /// Duplicated process asset transactions occurred. #[error("Encountered a duplicate process asset transaction: {0:?}")] EntryErrors(Vec), } @@ -62,10 +67,13 @@ pub enum ValidateLogError { /// An error that occurs when validating individual [`ProcessorTransactionLog`] entries. #[derive(Error, Debug)] pub enum LogEntryError { + /// A duplicate process asset transaction occurred for the given asset path. #[error("Encountered a duplicate process asset transaction: {0}")] DuplicateTransaction(AssetPath<'static>), + /// A transaction was ended that never started for the given asset path. #[error("A transaction was ended that never started {0}")] EndedMissingTransaction(AssetPath<'static>), + /// An asset started processing but never finished at the given asset path. #[error("An asset started processing but never finished: {0}")] UnfinishedTransaction(AssetPath<'static>), } diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index b37265d0fb660..d2202d2334d23 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, DeserializeMetaError, ErasedLoadedAsset, + AssetLoadError, AssetLoader, AssetPath, CompleteErasedLoadedAsset, DeserializeMetaError, 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 loaded_asset = server + let complete_asset = server .load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true) .await?; - for (path, full_hash) in &loaded_asset.loader_dependencies { + for (path, full_hash) in &complete_asset.asset.loader_dependencies { self.new_processed_info .process_dependencies .push(ProcessDependencyInfo { @@ -321,7 +321,7 @@ impl<'a> ProcessContext<'a> { path: path.to_owned(), }); } - Ok(loaded_asset) + Ok(complete_asset) } /// The path of the asset being processed. diff --git a/crates/bevy_asset/src/reflect.rs b/crates/bevy_asset/src/reflect.rs index 8347d19245b8f..5c436c10610f0 100644 --- a/crates/bevy_asset/src/reflect.rs +++ b/crates/bevy_asset/src/reflect.rs @@ -247,7 +247,6 @@ mod tests { use alloc::{string::String, vec::Vec}; use core::any::TypeId; - use crate as bevy_asset; use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle}; use bevy_app::App; use bevy_ecs::reflect::AppTypeRegistry; diff --git a/crates/bevy_asset/src/render_asset.rs b/crates/bevy_asset/src/render_asset.rs index 3bbc3dfd48458..5dac114c7f2ca 100644 --- a/crates/bevy_asset/src/render_asset.rs +++ b/crates/bevy_asset/src/render_asset.rs @@ -29,7 +29,9 @@ bitflags::bitflags! { #[reflect(opaque)] #[reflect(Serialize, Deserialize, Hash, PartialEq, Debug)] pub struct RenderAssetUsages: u8 { + /// The bit flag for the main world. const MAIN_WORLD = 1 << 0; + /// The bit flag for the render world. const RENDER_WORLD = 1 << 1; } } diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index c8b308a544640..3b187e96ef88a 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -1,6 +1,6 @@ use crate::{ io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader, - ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle, + CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle, }; use alloc::boxed::Box; use atomicow::CowArc; @@ -44,7 +44,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static { fn save<'a>( &'a self, writer: &'a mut Writer, - asset: &'a ErasedLoadedAsset, + complete_asset: &'a CompleteErasedLoadedAsset, 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, - asset: &'a ErasedLoadedAsset, + complete_asset: &'a CompleteErasedLoadedAsset, 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(asset).unwrap(); + let saved_asset = SavedAsset::::from_loaded(complete_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(asset: &'a ErasedLoadedAsset) -> Option { - let value = asset.value.downcast_ref::()?; + pub fn from_loaded(complete_asset: &'a CompleteErasedLoadedAsset) -> Option { + let value = complete_asset.asset.value.downcast_ref::()?; Some(SavedAsset { value, - labeled_assets: &asset.labeled_assets, + labeled_assets: &complete_asset.labeled_assets, }) } @@ -114,17 +114,13 @@ 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> + pub fn get_labeled(&self, label: &Q) -> Option<&B> where CowArc<'static, str>: Borrow, Q: ?Sized + Hash + Eq, { let labeled = self.labeled_assets.get(label)?; - let value = labeled.asset.value.downcast_ref::()?; - Some(SavedAsset { - value, - labeled_assets: &labeled.asset.labeled_assets, - }) + labeled.asset.value.downcast_ref::() } /// Returns the type-erased labeled asset, if it exists and matches this type. diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index e0571c8fd6355..1250ff666ae9f 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -352,7 +352,7 @@ mod tests { use bevy_reflect::TypePath; use bevy_tasks::block_on; - use crate::{self as bevy_asset, Asset}; + use crate::Asset; use super::*; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 0a5c59c629a24..e65bbd2b6acb7 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -14,8 +14,8 @@ use crate::{ }, path::AssetPath, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets, - DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, - UntypedAssetLoadFailedEvent, UntypedHandle, + CompleteErasedLoadedAsset, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, + UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle, }; use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec}; use alloc::{ @@ -697,12 +697,18 @@ 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 loaded_asset: ErasedLoadedAsset) { - for (_, labeled_asset) in loaded_asset.labeled_assets.drain() { - self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset); + 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, + }); } - self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset }); + self.send_asset_event(InternalAssetEvent::Loaded { + id, + loaded_asset: complete_asset.asset, + }); } /// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded. @@ -1326,7 +1332,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 = @@ -1731,6 +1737,10 @@ impl RecursiveDependencyLoadState { /// An error that occurs during an [`Asset`] load. #[derive(Error, Debug, Clone)] +#[expect( + missing_docs, + reason = "Adding docs to the variants would not add information beyond the error message and the names" +)] pub enum AssetLoadError { #[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")] RequestedHandleTypeMismatch { @@ -1792,6 +1802,7 @@ pub enum AssetLoadError { }, } +/// An error that can occur during asset loading. #[derive(Error, Debug, Clone)] #[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")] pub struct AssetLoaderError { @@ -1801,11 +1812,13 @@ pub struct AssetLoaderError { } impl AssetLoaderError { + /// The path of the asset that failed to load. pub fn path(&self) -> &AssetPath<'static> { &self.path } } +/// An error that occurs while resolving an asset added by `add_async`. #[derive(Error, Debug, Clone)] #[error("An error occurred while resolving an asset added by `add_async`: {error}")] pub struct AddAsyncError { @@ -1823,13 +1836,15 @@ pub struct MissingAssetLoaderForExtensionError { #[derive(Error, Debug, Clone, PartialEq, Eq)] #[error("no `AssetLoader` found with the name '{type_name}'")] pub struct MissingAssetLoaderForTypeNameError { - type_name: String, + /// The type name that was not found. + pub type_name: String, } /// An error that occurs when an [`AssetLoader`] is not registered for a given [`Asset`] [`TypeId`]. #[derive(Error, Debug, Clone, PartialEq, Eq)] #[error("no `AssetLoader` found with the ID '{type_id:?}'")] pub struct MissingAssetLoaderForTypeIdError { + /// The type ID that was not found. pub type_id: TypeId, } @@ -1860,10 +1875,13 @@ const UNTYPED_SOURCE_SUFFIX: &str = "--untyped"; /// An error when attempting to wait asynchronously for an [`Asset`] to load. #[derive(Error, Debug, Clone)] pub enum WaitForAssetError { + /// The asset is not being loaded; waiting for it is meaningless. #[error("tried to wait for an asset that is not being loaded")] NotLoaded, + /// The asset failed to load. #[error(transparent)] Failed(Arc), + /// A dependency of the asset failed to load. #[error(transparent)] DependencyFailed(Arc), } diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 8b2a8d09be84e..d2573582f16f0 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -1,4 +1,7 @@ -use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; +use crate::{ + meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, + UntypedHandle, +}; use alloc::boxed::Box; use atomicow::CowArc; use bevy_platform_support::collections::HashMap; @@ -56,11 +59,11 @@ impl DerefMut for TransformedAsset { impl TransformedAsset { /// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`. - pub fn from_loaded(asset: ErasedLoadedAsset) -> Option { - if let Ok(value) = asset.value.downcast::() { + pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option { + if let Ok(value) = complete_asset.asset.value.downcast::() { return Some(TransformedAsset { value: *value, - labeled_assets: asset.labeled_assets, + labeled_assets: complete_asset.labeled_assets, }); } None @@ -87,117 +90,13 @@ 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> - 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> - 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> + pub fn get_labeled(&mut self, label: &'_ Q) -> Option<&mut B> 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, - }) + labeled.asset.value.downcast_mut::() } /// Returns the type-erased labeled asset, if it exists and matches this type. pub fn get_erased_labeled(&self, label: &Q) -> Option<&ErasedLoadedAsset> @@ -254,6 +153,7 @@ pub struct IdentityAssetTransformer { } impl IdentityAssetTransformer { + /// Creates a new [`IdentityAssetTransformer`] with the correct internal [`PhantomData`] field. pub const fn new() -> Self { Self { _phantom: PhantomData, diff --git a/crates/bevy_audio/LICENSE-APACHE b/crates/bevy_audio/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_audio/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_audio/LICENSE-MIT b/crates/bevy_audio/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_audio/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_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 1d0149381bd57..93a0a7505ac60 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -74,7 +74,7 @@ impl PlaybackSettings { /// added again. pub const ONCE: PlaybackSettings = PlaybackSettings { mode: PlaybackMode::Once, - volume: Volume(1.0), + volume: Volume::Linear(1.0), speed: 1.0, paused: false, muted: false, diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index c098ac8382b4d..1869fb47555db 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -170,7 +170,7 @@ pub(crate) fn play_queued_audio_system( } sink.set_speed(settings.speed); - sink.set_volume(settings.volume.0 * global_volume.volume.0); + sink.set_volume(settings.volume * global_volume.volume); if settings.paused { sink.pause(); @@ -210,7 +210,7 @@ pub(crate) fn play_queued_audio_system( } sink.set_speed(settings.speed); - sink.set_volume(settings.volume.0 * global_volume.volume.0); + sink.set_volume(settings.volume * global_volume.volume); if settings.paused { sink.pause(); diff --git a/crates/bevy_audio/src/sinks.rs b/crates/bevy_audio/src/sinks.rs index d4be43261fde0..b0c77456e1db6 100644 --- a/crates/bevy_audio/src/sinks.rs +++ b/crates/bevy_audio/src/sinks.rs @@ -3,37 +3,26 @@ use bevy_math::Vec3; use bevy_transform::prelude::Transform; use rodio::{Sink, SpatialSink}; +use crate::Volume; + /// Common interactions with an audio sink. pub trait AudioSinkPlayback { - /// Gets the volume of the sound. - /// - /// The value `1.0` is the "normal" volume (unfiltered input). Any value - /// other than `1.0` will multiply each sample by this value. + /// Gets the volume of the sound as a [`Volume`]. /// /// If the sink is muted, this returns the managed volume rather than the - /// sink's actual volume. This allows you to use the volume as if the sink - /// were not muted, because a muted sink has a volume of 0. - fn volume(&self) -> f32; + /// sink's actual volume. This allows you to use the returned volume as if + /// the sink were not muted, because a muted sink has a physical volume of + /// 0. + fn volume(&self) -> Volume; - /// Changes the volume of the sound. - /// - /// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` - /// will multiply each sample by this value. + /// Changes the volume of the sound to the given [`Volume`]. /// /// If the sink is muted, changing the volume won't unmute it, i.e. the - /// sink's volume will remain at `0.0`. However, the sink will remember the - /// volume change and it will be used when [`unmute`](Self::unmute) is - /// called. This allows you to control the volume even when the sink is - /// muted. - /// - /// # Note on Audio Volume - /// - /// An increase of 10 decibels (dB) roughly corresponds to the perceived volume doubling in intensity. - /// As this function scales not the volume but the amplitude, a conversion might be necessary. - /// For example, to halve the perceived volume you need to decrease the volume by 10 dB. - /// This corresponds to 20log(x) = -10dB, solving x = 10^(-10/20) = 0.316. - /// Multiply the current volume by 0.316 to halve the perceived volume. - fn set_volume(&mut self, volume: f32); + /// sink's volume will remain "off" / "muted". However, the sink will + /// remember the volume change and it will be used when + /// [`unmute`](Self::unmute) is called. This allows you to control the + /// volume even when the sink is muted. + fn set_volume(&mut self, volume: Volume); /// Gets the speed of the sound. /// @@ -132,7 +121,7 @@ pub struct AudioSink { /// If the sink is muted, this is `Some(volume)` where `volume` is the /// user's intended volume setting, even if the underlying sink's volume is /// 0. - pub(crate) managed_volume: Option, + pub(crate) managed_volume: Option, } impl AudioSink { @@ -146,15 +135,16 @@ impl AudioSink { } impl AudioSinkPlayback for AudioSink { - fn volume(&self) -> f32 { - self.managed_volume.unwrap_or_else(|| self.sink.volume()) + fn volume(&self) -> Volume { + self.managed_volume + .unwrap_or_else(|| Volume::Linear(self.sink.volume())) } - fn set_volume(&mut self, volume: f32) { + fn set_volume(&mut self, volume: Volume) { if self.is_muted() { self.managed_volume = Some(volume); } else { - self.sink.set_volume(volume); + self.sink.set_volume(volume.to_linear()); } } @@ -197,7 +187,7 @@ impl AudioSinkPlayback for AudioSink { fn unmute(&mut self) { if let Some(volume) = self.managed_volume.take() { - self.sink.set_volume(volume); + self.sink.set_volume(volume.to_linear()); } } } @@ -227,7 +217,7 @@ pub struct SpatialAudioSink { /// If the sink is muted, this is `Some(volume)` where `volume` is the /// user's intended volume setting, even if the underlying sink's volume is /// 0. - pub(crate) managed_volume: Option, + pub(crate) managed_volume: Option, } impl SpatialAudioSink { @@ -241,15 +231,16 @@ impl SpatialAudioSink { } impl AudioSinkPlayback for SpatialAudioSink { - fn volume(&self) -> f32 { - self.managed_volume.unwrap_or_else(|| self.sink.volume()) + fn volume(&self) -> Volume { + self.managed_volume + .unwrap_or_else(|| Volume::Linear(self.sink.volume())) } - fn set_volume(&mut self, volume: f32) { + fn set_volume(&mut self, volume: Volume) { if self.is_muted() { self.managed_volume = Some(volume); } else { - self.sink.set_volume(volume); + self.sink.set_volume(volume.to_linear()); } } @@ -292,7 +283,7 @@ impl AudioSinkPlayback for SpatialAudioSink { fn unmute(&mut self) { if let Some(volume) = self.managed_volume.take() { - self.sink.set_volume(volume); + self.sink.set_volume(volume.to_linear()); } } } @@ -326,11 +317,11 @@ mod tests { fn test_audio_sink_playback(mut audio_sink: T) { // Test volume - assert_eq!(audio_sink.volume(), 1.0); // default volume - audio_sink.set_volume(0.5); - assert_eq!(audio_sink.volume(), 0.5); - audio_sink.set_volume(1.0); - assert_eq!(audio_sink.volume(), 1.0); + assert_eq!(audio_sink.volume(), Volume::Linear(1.0)); // default volume + audio_sink.set_volume(Volume::Linear(0.5)); + assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); + audio_sink.set_volume(Volume::Linear(1.0)); + assert_eq!(audio_sink.volume(), Volume::Linear(1.0)); // Test speed assert_eq!(audio_sink.speed(), 1.0); // default speed @@ -361,11 +352,11 @@ mod tests { assert!(!audio_sink.is_muted()); // Test volume with mute - audio_sink.set_volume(0.5); + audio_sink.set_volume(Volume::Linear(0.5)); audio_sink.mute(); - assert_eq!(audio_sink.volume(), 0.5); // returns managed volume even though sink volume is 0 + assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // returns managed volume even though sink volume is 0 audio_sink.unmute(); - assert_eq!(audio_sink.volume(), 0.5); // managed volume is restored + assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // managed volume is restored // Test toggle mute audio_sink.toggle_mute(); diff --git a/crates/bevy_audio/src/volume.rs b/crates/bevy_audio/src/volume.rs index f12fe0497fe3f..0fb43f15efbf2 100644 --- a/crates/bevy_audio/src/volume.rs +++ b/crates/bevy_audio/src/volume.rs @@ -1,48 +1,504 @@ -use bevy_derive::Deref; use bevy_ecs::prelude::*; +use bevy_math::ops; use bevy_reflect::prelude::*; /// Use this [`Resource`] to control the global volume of all audio. /// -/// Note: changing this value will not affect already playing audio. -#[derive(Resource, Default, Clone, Copy, Reflect)] -#[reflect(Resource, Default)] +/// Note: Changing [`GlobalVolume`] does not affect already playing audio. +#[derive(Resource, Debug, Default, Clone, Copy, Reflect)] +#[reflect(Resource, Debug, Default)] pub struct GlobalVolume { /// The global volume of all audio. pub volume: Volume, } +impl From for GlobalVolume { + fn from(volume: Volume) -> Self { + Self { volume } + } +} + impl GlobalVolume { /// Create a new [`GlobalVolume`] with the given volume. - pub fn new(volume: f32) -> Self { - Self { - volume: Volume::new(volume), - } + pub fn new(volume: Volume) -> Self { + Self { volume } } } -/// A volume level equivalent to a non-negative float. -#[derive(Clone, Copy, Deref, Debug, Reflect)] -#[reflect(Debug)] -pub struct Volume(pub(crate) f32); +/// A [`Volume`] represents an audio source's volume level. +/// +/// To create a new [`Volume`] from a linear scale value, use +/// [`Volume::Linear`]. +/// +/// To create a new [`Volume`] from decibels, use [`Volume::Decibels`]. +#[derive(Clone, Copy, Debug, Reflect)] +#[reflect(Debug, PartialEq)] +pub enum Volume { + /// Create a new [`Volume`] from the given volume in linear scale. + /// + /// In a linear scale, the value `1.0` represents the "normal" volume, + /// meaning the audio is played at its original level. Values greater than + /// `1.0` increase the volume, while values between `0.0` and `1.0` decrease + /// the volume. A value of `0.0` effectively mutes the audio. + /// + /// # Examples + /// + /// ``` + /// # use bevy_audio::Volume; + /// # use bevy_math::ops; + /// # + /// # const EPSILON: f32 = 0.01; + /// + /// let volume = Volume::Linear(0.5); + /// assert_eq!(volume.to_linear(), 0.5); + /// assert!(ops::abs(volume.to_decibels() - -6.0206) < EPSILON); + /// + /// let volume = Volume::Linear(0.0); + /// assert_eq!(volume.to_linear(), 0.0); + /// assert_eq!(volume.to_decibels(), f32::NEG_INFINITY); + /// + /// let volume = Volume::Linear(1.0); + /// assert_eq!(volume.to_linear(), 1.0); + /// assert!(ops::abs(volume.to_decibels() - 0.0) < EPSILON); + /// ``` + Linear(f32), + /// Create a new [`Volume`] from the given volume in decibels. + /// + /// In a decibel scale, the value `0.0` represents the "normal" volume, + /// meaning the audio is played at its original level. Values greater than + /// `0.0` increase the volume, while values less than `0.0` decrease the + /// volume. A value of [`f32::NEG_INFINITY`] decibels effectively mutes the + /// audio. + /// + /// # Examples + /// + /// ``` + /// # use bevy_audio::Volume; + /// # use bevy_math::ops; + /// # + /// # const EPSILON: f32 = 0.01; + /// + /// let volume = Volume::Decibels(-5.998); + /// assert!(ops::abs(volume.to_linear() - 0.5) < EPSILON); + /// + /// let volume = Volume::Decibels(f32::NEG_INFINITY); + /// assert_eq!(volume.to_linear(), 0.0); + /// + /// let volume = Volume::Decibels(0.0); + /// assert_eq!(volume.to_linear(), 1.0); + /// + /// let volume = Volume::Decibels(20.0); + /// assert_eq!(volume.to_linear(), 10.0); + /// ``` + Decibels(f32), +} impl Default for Volume { fn default() -> Self { - Self(1.0) + Self::Linear(1.0) + } +} + +impl PartialEq for Volume { + fn eq(&self, other: &Self) -> bool { + use Volume::{Decibels, Linear}; + + match (self, other) { + (Linear(a), Linear(b)) => a.abs() == b.abs(), + (Decibels(a), Decibels(b)) => a == b, + (a, b) => a.to_decibels() == b.to_decibels(), + } + } +} + +impl PartialOrd for Volume { + fn partial_cmp(&self, other: &Self) -> Option { + use Volume::{Decibels, Linear}; + + Some(match (self, other) { + (Linear(a), Linear(b)) => a.abs().total_cmp(&b.abs()), + (Decibels(a), Decibels(b)) => a.total_cmp(b), + (a, b) => a.to_decibels().total_cmp(&b.to_decibels()), + }) } } +#[inline] +fn decibels_to_linear(decibels: f32) -> f32 { + ops::powf(10.0f32, decibels / 20.0) +} + +#[inline] +fn linear_to_decibels(linear: f32) -> f32 { + 20.0 * ops::log10(linear.abs()) +} + impl Volume { - /// Create a new volume level. - pub fn new(volume: f32) -> Self { - debug_assert!(volume >= 0.0); - Self(f32::max(volume, 0.)) + /// Returns the volume in linear scale as a float. + pub fn to_linear(&self) -> f32 { + match self { + Self::Linear(v) => v.abs(), + Self::Decibels(v) => decibels_to_linear(*v), + } + } + + /// Returns the volume in decibels as a float. + /// + /// If the volume is silent / off / muted, i.e. it's underlying linear scale + /// is `0.0`, this method returns negative infinity. + pub fn to_decibels(&self) -> f32 { + match self { + Self::Linear(v) => linear_to_decibels(*v), + Self::Decibels(v) => *v, + } + } + + /// The silent volume. Also known as "off" or "muted". + pub const SILENT: Self = Volume::Linear(0.0); +} + +impl core::ops::Add for Volume { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + use Volume::{Decibels, Linear}; + + match (self, rhs) { + (Linear(a), Linear(b)) => Linear(a + b), + (Decibels(a), Decibels(b)) => Decibels(linear_to_decibels( + decibels_to_linear(a) + decibels_to_linear(b), + )), + // {Linear, Decibels} favors the left hand side of the operation by + // first converting the right hand side to the same type as the left + // hand side and then performing the operation. + (Linear(..), Decibels(db)) => self + Linear(decibels_to_linear(db)), + (Decibels(..), Linear(l)) => self + Decibels(linear_to_decibels(l)), + } + } +} + +impl core::ops::AddAssign for Volume { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl core::ops::Sub for Volume { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + use Volume::{Decibels, Linear}; + + match (self, rhs) { + (Linear(a), Linear(b)) => Linear(a - b), + (Decibels(a), Decibels(b)) => Decibels(linear_to_decibels( + decibels_to_linear(a) - decibels_to_linear(b), + )), + // {Linear, Decibels} favors the left hand side of the operation by + // first converting the right hand side to the same type as the left + // hand side and then performing the operation. + (Linear(..), Decibels(db)) => self - Linear(decibels_to_linear(db)), + (Decibels(..), Linear(l)) => self - Decibels(linear_to_decibels(l)), + } } - /// Get the value of the volume level. - pub fn get(&self) -> f32 { - self.0 +} + +impl core::ops::SubAssign for Volume { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; } +} + +impl core::ops::Mul for Volume { + type Output = Self; + + fn mul(self, rhs: Self) -> Self { + use Volume::{Decibels, Linear}; + + match (self, rhs) { + (Linear(a), Linear(b)) => Linear(a * b), + (Decibels(a), Decibels(b)) => Decibels(a + b), + // {Linear, Decibels} favors the left hand side of the operation by + // first converting the right hand side to the same type as the left + // hand side and then performing the operation. + (Linear(..), Decibels(db)) => self * Linear(decibels_to_linear(db)), + (Decibels(..), Linear(l)) => self * Decibels(linear_to_decibels(l)), + } + } +} + +impl core::ops::MulAssign for Volume { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} - /// Zero (silent) volume level - pub const ZERO: Self = Volume(0.0); +impl core::ops::Div for Volume { + type Output = Self; + + fn div(self, rhs: Self) -> Self { + use Volume::{Decibels, Linear}; + + match (self, rhs) { + (Linear(a), Linear(b)) => Linear(a / b), + (Decibels(a), Decibels(b)) => Decibels(a - b), + // {Linear, Decibels} favors the left hand side of the operation by + // first converting the right hand side to the same type as the left + // hand side and then performing the operation. + (Linear(..), Decibels(db)) => self / Linear(decibels_to_linear(db)), + (Decibels(..), Linear(l)) => self / Decibels(linear_to_decibels(l)), + } + } +} + +impl core::ops::DivAssign for Volume { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +#[cfg(test)] +mod tests { + use super::Volume::{self, Decibels, Linear}; + + /// Based on [Wikipedia's Decibel article]. + /// + /// [Wikipedia's Decibel article]: https://web.archive.org/web/20230810185300/https://en.wikipedia.org/wiki/Decibel + const DECIBELS_LINEAR_TABLE: [(f32, f32); 27] = [ + (100., 100000.), + (90., 31623.), + (80., 10000.), + (70., 3162.), + (60., 1000.), + (50., 316.2), + (40., 100.), + (30., 31.62), + (20., 10.), + (10., 3.162), + (5.998, 1.995), + (3.003, 1.413), + (1.002, 1.122), + (0., 1.), + (-1.002, 0.891), + (-3.003, 0.708), + (-5.998, 0.501), + (-10., 0.3162), + (-20., 0.1), + (-30., 0.03162), + (-40., 0.01), + (-50., 0.003162), + (-60., 0.001), + (-70., 0.0003162), + (-80., 0.0001), + (-90., 0.00003162), + (-100., 0.00001), + ]; + + #[test] + fn volume_conversion() { + for (db, linear) in DECIBELS_LINEAR_TABLE { + for volume in [Linear(linear), Decibels(db), Linear(-linear)] { + let db_test = volume.to_decibels(); + let linear_test = volume.to_linear(); + + let db_delta = db_test - db; + let linear_relative_delta = (linear_test - linear) / linear; + + assert!( + db_delta.abs() < 1e-2, + "Expected ~{}dB, got {}dB (delta {})", + db, + db_test, + db_delta + ); + assert!( + linear_relative_delta.abs() < 1e-3, + "Expected ~{}, got {} (relative delta {})", + linear, + linear_test, + linear_relative_delta + ); + } + } + } + + #[test] + fn volume_conversion_special() { + assert!( + Decibels(f32::INFINITY).to_linear().is_infinite(), + "Infinite decibels is equivalent to infinite linear scale" + ); + assert!( + Linear(f32::INFINITY).to_decibels().is_infinite(), + "Infinite linear scale is equivalent to infinite decibels" + ); + + assert!( + Linear(f32::NEG_INFINITY).to_decibels().is_infinite(), + "Negative infinite linear scale is equivalent to infinite decibels" + ); + assert!( + Decibels(f32::NEG_INFINITY).to_linear().abs() == 0.0, + "Negative infinity decibels is equivalent to zero linear scale" + ); + + assert!( + Linear(0.0).to_decibels().is_infinite(), + "Zero linear scale is equivalent to negative infinity decibels" + ); + assert!( + Linear(-0.0).to_decibels().is_infinite(), + "Negative zero linear scale is equivalent to negative infinity decibels" + ); + + assert!( + Decibels(f32::NAN).to_linear().is_nan(), + "NaN decibels is equivalent to NaN linear scale" + ); + assert!( + Linear(f32::NAN).to_decibels().is_nan(), + "NaN linear scale is equivalent to NaN decibels" + ); + } + + fn assert_approx_eq(a: Volume, b: Volume) { + const EPSILON: f32 = 0.0001; + + match (a, b) { + (Decibels(a), Decibels(b)) | (Linear(a), Linear(b)) => assert!( + (a - b).abs() < EPSILON, + "Expected {:?} to be approximately equal to {:?}", + a, + b + ), + (a, b) => assert!( + (a.to_decibels() - b.to_decibels()).abs() < EPSILON, + "Expected {:?} to be approximately equal to {:?}", + a, + b + ), + } + } + + #[test] + fn volume_ops_add() { + // Linear to Linear. + assert_approx_eq(Linear(0.5) + Linear(0.5), Linear(1.0)); + assert_approx_eq(Linear(0.5) + Linear(0.1), Linear(0.6)); + assert_approx_eq(Linear(0.5) + Linear(-0.5), Linear(0.0)); + + // Decibels to Decibels. + assert_approx_eq(Decibels(0.0) + Decibels(0.0), Decibels(6.0206003)); + assert_approx_eq(Decibels(6.0) + Decibels(6.0), Decibels(12.020599)); + assert_approx_eq(Decibels(-6.0) + Decibels(-6.0), Decibels(0.020599423)); + + // {Linear, Decibels} favors the left hand side of the operation. + assert_approx_eq(Linear(0.5) + Decibels(0.0), Linear(1.5)); + assert_approx_eq(Decibels(0.0) + Linear(0.5), Decibels(3.521825)); + } + + #[test] + fn volume_ops_add_assign() { + // Linear to Linear. + let mut volume = Linear(0.5); + volume += Linear(0.5); + assert_approx_eq(volume, Linear(1.0)); + } + + #[test] + fn volume_ops_sub() { + // Linear to Linear. + assert_approx_eq(Linear(0.5) - Linear(0.5), Linear(0.0)); + assert_approx_eq(Linear(0.5) - Linear(0.1), Linear(0.4)); + assert_approx_eq(Linear(0.5) - Linear(-0.5), Linear(1.0)); + + // Decibels to Decibels. + assert_eq!(Decibels(0.0) - Decibels(0.0), Decibels(f32::NEG_INFINITY)); + assert_approx_eq(Decibels(6.0) - Decibels(4.0), Decibels(-7.736506)); + assert_eq!(Decibels(-6.0) - Decibels(-6.0), Decibels(f32::NEG_INFINITY)); + } + + #[test] + fn volume_ops_sub_assign() { + // Linear to Linear. + let mut volume = Linear(0.5); + volume -= Linear(0.5); + assert_approx_eq(volume, Linear(0.0)); + } + + #[test] + fn volume_ops_mul() { + // Linear to Linear. + assert_approx_eq(Linear(0.5) * Linear(0.5), Linear(0.25)); + assert_approx_eq(Linear(0.5) * Linear(0.1), Linear(0.05)); + assert_approx_eq(Linear(0.5) * Linear(-0.5), Linear(-0.25)); + + // Decibels to Decibels. + assert_approx_eq(Decibels(0.0) * Decibels(0.0), Decibels(0.0)); + assert_approx_eq(Decibels(6.0) * Decibels(6.0), Decibels(12.0)); + assert_approx_eq(Decibels(-6.0) * Decibels(-6.0), Decibels(-12.0)); + + // {Linear, Decibels} favors the left hand side of the operation. + assert_approx_eq(Linear(0.5) * Decibels(0.0), Linear(0.5)); + assert_approx_eq(Decibels(0.0) * Linear(0.501), Decibels(-6.003246)); + } + + #[test] + fn volume_ops_mul_assign() { + // Linear to Linear. + let mut volume = Linear(0.5); + volume *= Linear(0.5); + assert_approx_eq(volume, Linear(0.25)); + + // Decibels to Decibels. + let mut volume = Decibels(6.0); + volume *= Decibels(6.0); + assert_approx_eq(volume, Decibels(12.0)); + + // {Linear, Decibels} favors the left hand side of the operation. + let mut volume = Linear(0.5); + volume *= Decibels(0.0); + assert_approx_eq(volume, Linear(0.5)); + let mut volume = Decibels(0.0); + volume *= Linear(0.501); + assert_approx_eq(volume, Decibels(-6.003246)); + } + + #[test] + fn volume_ops_div() { + // Linear to Linear. + assert_approx_eq(Linear(0.5) / Linear(0.5), Linear(1.0)); + assert_approx_eq(Linear(0.5) / Linear(0.1), Linear(5.0)); + assert_approx_eq(Linear(0.5) / Linear(-0.5), Linear(-1.0)); + + // Decibels to Decibels. + assert_approx_eq(Decibels(0.0) / Decibels(0.0), Decibels(0.0)); + assert_approx_eq(Decibels(6.0) / Decibels(6.0), Decibels(0.0)); + assert_approx_eq(Decibels(-6.0) / Decibels(-6.0), Decibels(0.0)); + + // {Linear, Decibels} favors the left hand side of the operation. + assert_approx_eq(Linear(0.5) / Decibels(0.0), Linear(0.5)); + assert_approx_eq(Decibels(0.0) / Linear(0.501), Decibels(6.003246)); + } + + #[test] + fn volume_ops_div_assign() { + // Linear to Linear. + let mut volume = Linear(0.5); + volume /= Linear(0.5); + assert_approx_eq(volume, Linear(1.0)); + + // Decibels to Decibels. + let mut volume = Decibels(6.0); + volume /= Decibels(6.0); + assert_approx_eq(volume, Decibels(0.0)); + + // {Linear, Decibels} favors the left hand side of the operation. + let mut volume = Linear(0.5); + volume /= Decibels(0.0); + assert_approx_eq(volume, Linear(0.5)); + let mut volume = Decibels(0.0); + volume /= Linear(0.501); + assert_approx_eq(volume, Decibels(6.003246)); + } } diff --git a/crates/bevy_color/Cargo.toml b/crates/bevy_color/Cargo.toml index 324579cecb02f..7115963e7f025 100644 --- a/crates/bevy_color/Cargo.toml +++ b/crates/bevy_color/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1.0", features = [ ], default-features = false, optional = true } thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = ["from"] } -wgpu-types = { version = "23", default-features = false, optional = true } +wgpu-types = { version = "24", default-features = false, optional = true } encase = { version = "0.10", default-features = false, optional = true } [features] diff --git a/crates/bevy_color/LICENSE-APACHE b/crates/bevy_color/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_color/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_color/LICENSE-MIT b/crates/bevy_color/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_color/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_core_pipeline/LICENSE-APACHE b/crates/bevy_core_pipeline/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_core_pipeline/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_core_pipeline/LICENSE-MIT b/crates/bevy_core_pipeline/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_core_pipeline/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_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index 87d6abd8cf8c5..06fa118827fe7 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::*; +use bevy_asset::{prelude::*, weak_handle}; use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_render::{ @@ -44,7 +44,8 @@ pub enum AutoExposurePass { Average, } -pub const METERING_SHADER_HANDLE: Handle = Handle::weak_from_u128(12987620402995522466); +pub const METERING_SHADER_HANDLE: Handle = + weak_handle!("05c84384-afa4-41d9-844e-e9cd5e7609af"); pub const HISTOGRAM_BIN_COUNT: u64 = 64; diff --git a/crates/bevy_core_pipeline/src/blit/mod.rs b/crates/bevy_core_pipeline/src/blit/mod.rs index 96c0394f3034a..53c54c6d2d7aa 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::prelude::*; use bevy_render::{ render_resource::{ @@ -12,7 +12,7 @@ use bevy_render::{ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; -pub const BLIT_SHADER_HANDLE: Handle = Handle::weak_from_u128(2312396983770133547); +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; diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 8f242931d71cf..938f2f243be81 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -10,7 +10,7 @@ use crate::{ core_3d::graph::{Core3d, Node3d}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_math::{ops, UVec2}; use bevy_render::{ @@ -34,7 +34,7 @@ use upsampling_pipeline::{ prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds, }; -const BLOOM_SHADER_HANDLE: Handle = Handle::weak_from_u128(929599476923908); +const BLOOM_SHADER_HANDLE: Handle = weak_handle!("c9190ddc-573b-4472-8b21-573cab502b73"); const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Ufloat; diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs index 7067058833c7c..586108469451f 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs @@ -4,7 +4,7 @@ use crate::{ fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Handle}; +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}; @@ -96,7 +96,7 @@ impl ExtractComponent for ContrastAdaptiveSharpening { } const CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE: Handle = - Handle::weak_from_u128(6925381244141981602); + weak_handle!("ef83f0a5-51df-4b51-9ab7-b5fd1ae5a397"); /// Adds Support for Contrast Adaptive Sharpening (CAS). pub struct CasPlugin; diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index bce42cead700e..dae9217d3a867 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -4,8 +4,8 @@ use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::ViewSortedRenderPhases, - render_resource::{RenderPassDescriptor, StoreOp}, + render_phase::{TrackedRenderPass, ViewSortedRenderPhases}, + render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, renderer::RenderContext, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; @@ -42,63 +42,75 @@ impl ViewNode for MainTransparentPass2dNode { return Ok(()); }; - // This needs to run at least once to clear the background color, even if there are no items to render - { - #[cfg(feature = "trace")] - let _main_pass_2d = info_span!("main_transparent_pass_2d").entered(); - - let diagnostics = render_context.diagnostic_recorder(); - - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("main_transparent_pass_2d"), - color_attachments: &[Some(target.get_color_attachment())], - // NOTE: For the transparent pass we load the depth buffer. There should be no - // need to write to it, but store is set to `true` as a workaround for issue #3776, - // https://github.com/bevyengine/bevy/issues/3776 - // so that wgpu does not clear the depth buffer. - // As the opaque and alpha mask passes run first, opaque meshes can occlude - // transparent ones. - depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), - timestamp_writes: None, - occlusion_query_set: None, - }); - - let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_2d"); - - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } + let diagnostics = render_context.diagnostic_recorder(); + + render_context.add_command_buffer_generation_task(move |render_device| { + // Command encoder setup + let mut command_encoder = + render_device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("main_transparent_pass_2d_command_encoder"), + }); - if !transparent_phase.items.is_empty() { + // This needs to run at least once to clear the background color, even if there are no items to render + { #[cfg(feature = "trace")] - let _transparent_main_pass_2d_span = - info_span!("transparent_main_pass_2d").entered(); - if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the transparent 2D phase {err:?}"); + let _main_pass_2d = info_span!("main_transparent_pass_2d").entered(); + + let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("main_transparent_pass_2d"), + color_attachments: &[Some(target.get_color_attachment())], + // NOTE: For the transparent pass we load the depth buffer. There should be no + // need to write to it, but store is set to `true` as a workaround for issue #3776, + // https://github.com/bevyengine/bevy/issues/3776 + // so that wgpu does not clear the depth buffer. + // As the opaque and alpha mask passes run first, opaque meshes can occlude + // transparent ones. + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); + let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); + + let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_2d"); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if !transparent_phase.items.is_empty() { + #[cfg(feature = "trace")] + let _transparent_main_pass_2d_span = + info_span!("transparent_main_pass_2d").entered(); + if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) + { + error!( + "Error encountered while rendering the transparent 2D phase {err:?}" + ); + } } + + pass_span.end(&mut render_pass); + } + + // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't + // reset for the next render pass so add an empty render pass without a custom viewport + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + if camera.viewport.is_some() { + #[cfg(feature = "trace")] + let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); + let pass_descriptor = RenderPassDescriptor { + label: Some("reset_viewport_pass_2d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + command_encoder.begin_render_pass(&pass_descriptor); } - pass_span.end(&mut render_pass); - } - - // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't - // reset for the next render pass so add an empty render pass without a custom viewport - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - if camera.viewport.is_some() { - #[cfg(feature = "trace")] - let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("reset_viewport_pass_2d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - } + command_encoder.finish() + }); Ok(()) } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f820a245cecff..f32775727772f 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -436,8 +436,9 @@ pub fn extract_core_2d_camera_phases( let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); transparent_2d_phases.insert_or_clear(retained_view_entity); - opaque_2d_phases.insert_or_clear(retained_view_entity, GpuPreprocessingMode::None); - alpha_mask_2d_phases.insert_or_clear(retained_view_entity, GpuPreprocessingMode::None); + opaque_2d_phases.prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); + alpha_mask_2d_phases + .prepare_for_new_frame(retained_view_entity, GpuPreprocessingMode::None); live_entities.insert(retained_view_entity); } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 0d6df5c8873ae..fa092b7fd494c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -19,7 +19,8 @@ pub mod graph { EarlyPrepass, EarlyDownsampleDepth, LatePrepass, - DeferredPrepass, + EarlyDeferredPrepass, + LateDeferredPrepass, CopyDeferredLightingId, EndPrepasses, StartMainPass, @@ -112,7 +113,8 @@ use tracing::warn; use crate::{ core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode, deferred::{ - copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode, + copy_lighting_id::CopyDeferredLightingIdNode, + node::{EarlyDeferredGBufferPrepassNode, LateDeferredGBufferPrepassNode}, AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT, }, @@ -179,9 +181,13 @@ impl Plugin for Core3dPlugin { .add_render_sub_graph(Core3d) .add_render_graph_node::>(Core3d, Node3d::EarlyPrepass) .add_render_graph_node::>(Core3d, Node3d::LatePrepass) - .add_render_graph_node::>( + .add_render_graph_node::>( Core3d, - Node3d::DeferredPrepass, + Node3d::EarlyDeferredPrepass, + ) + .add_render_graph_node::>( + Core3d, + Node3d::LateDeferredPrepass, ) .add_render_graph_node::>( Core3d, @@ -210,8 +216,9 @@ impl Plugin for Core3dPlugin { Core3d, ( Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::LatePrepass, - Node3d::DeferredPrepass, + Node3d::LateDeferredPrepass, Node3d::CopyDeferredLightingId, Node3d::EndPrepasses, Node3d::StartMainPass, @@ -629,8 +636,8 @@ pub fn extract_core_3d_camera_phases( // This is the main 3D camera, so use the first subview index (0). let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); - opaque_3d_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); - alpha_mask_3d_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + opaque_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); + alpha_mask_3d_phases.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); transmissive_3d_phases.insert_or_clear(retained_view_entity); transparent_3d_phases.insert_or_clear(retained_view_entity); @@ -698,31 +705,55 @@ pub fn extract_camera_prepass_phase( let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0); if depth_prepass || normal_prepass || motion_vector_prepass { - opaque_3d_prepass_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + opaque_3d_prepass_phases + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); alpha_mask_3d_prepass_phases - .insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); } else { opaque_3d_prepass_phases.remove(&retained_view_entity); alpha_mask_3d_prepass_phases.remove(&retained_view_entity); } if deferred_prepass { - opaque_3d_deferred_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + opaque_3d_deferred_phases + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); alpha_mask_3d_deferred_phases - .insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); } else { opaque_3d_deferred_phases.remove(&retained_view_entity); alpha_mask_3d_deferred_phases.remove(&retained_view_entity); } live_entities.insert(retained_view_entity); - commands + // Add or remove prepasses as appropriate. + + let mut camera_commands = commands .get_entity(entity) - .expect("Camera entity wasn't synced.") - .insert_if(DepthPrepass, || depth_prepass) - .insert_if(NormalPrepass, || normal_prepass) - .insert_if(MotionVectorPrepass, || motion_vector_prepass) - .insert_if(DeferredPrepass, || deferred_prepass); + .expect("Camera entity wasn't synced."); + + if depth_prepass { + camera_commands.insert(DepthPrepass); + } else { + camera_commands.remove::(); + } + + if normal_prepass { + camera_commands.insert(NormalPrepass); + } else { + camera_commands.remove::(); + } + + if motion_vector_prepass { + camera_commands.insert(MotionVectorPrepass); + } else { + camera_commands.remove::(); + } + + if deferred_prepass { + camera_commands.insert(DeferredPrepass); + } else { + camera_commands.remove::(); + } } opaque_3d_prepass_phases.retain(|view_entity, _| live_entities.contains(view_entity)); @@ -919,7 +950,6 @@ fn configure_occlusion_culling_view_targets( With, Without, With, - Without, ), >, ) { @@ -984,6 +1014,7 @@ pub fn prepare_prepass_textures( && !opaque_3d_deferred_phases.contains_key(&view.retained_view_entity) && !alpha_mask_3d_deferred_phases.contains_key(&view.retained_view_entity) { + commands.entity(entity).remove::(); continue; }; 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 f645d22092bfa..966be880c28ce 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::prelude::*; use bevy_math::UVec2; use bevy_render::{ @@ -24,7 +24,7 @@ use bevy_render::{ use super::DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT; pub const COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE: Handle = - Handle::weak_from_u128(5230948520734987); + weak_handle!("70d91342-1c43-4b20-973f-aa6ce93aa617"); pub struct CopyDeferredLightingIdPlugin; impl Plugin for CopyDeferredLightingIdPlugin { diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 5485e8fc0034d..ffac1eec6de82 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,7 +1,8 @@ use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_render::experimental::occlusion_culling::OcclusionCulling; use bevy_render::render_graph::ViewNode; -use bevy_render::view::ExtractedView; +use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext}, @@ -18,76 +19,151 @@ use crate::prepass::ViewPrepassTextures; use super::{AlphaMask3dDeferred, Opaque3dDeferred}; -/// Render node used by the prepass. +/// The phase of the deferred prepass that draws meshes that were visible last +/// frame. /// -/// By default, inserted before the main pass in the render graph. +/// If occlusion culling isn't in use, this prepass simply draws all meshes. +/// +/// Like all prepass nodes, this is inserted before the main pass in the render +/// graph. #[derive(Default)] -pub struct DeferredGBufferPrepassNode; +pub struct EarlyDeferredGBufferPrepassNode; + +impl ViewNode for EarlyDeferredGBufferPrepassNode { + type ViewQuery = ::ViewQuery; + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + view_query: QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + run_deferred_prepass( + graph, + render_context, + view_query, + false, + world, + "early deferred prepass", + ) + } +} -impl ViewNode for DeferredGBufferPrepassNode { +/// The phase of the prepass that runs after occlusion culling against the +/// meshes that were visible last frame. +/// +/// If occlusion culling isn't in use, this is a no-op. +/// +/// Like all prepass nodes, this is inserted before the main pass in the render +/// graph. +#[derive(Default)] +pub struct LateDeferredGBufferPrepassNode; + +impl ViewNode for LateDeferredGBufferPrepassNode { type ViewQuery = ( &'static ExtractedCamera, &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, + Has, + Has, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, extracted_view, view_depth_texture, view_prepass_textures): QueryItem< - 'w, - Self::ViewQuery, - >, + view_query: QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { + let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query; + if !occlusion_culling || no_indirect_drawing { return Ok(()); - }; + } - let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = ( - opaque_deferred_phases.get(&extracted_view.retained_view_entity), - alpha_mask_deferred_phases.get(&extracted_view.retained_view_entity), - ) else { - return Ok(()); - }; - - let mut color_attachments = vec![]; - color_attachments.push( - view_prepass_textures - .normal - .as_ref() - .map(|normals_texture| normals_texture.get_attachment()), - ); - color_attachments.push( - view_prepass_textures - .motion_vectors - .as_ref() - .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), - ); - - // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: - // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. - // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT. - // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9 - // For webgl2 we fallback to manually clearing - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + run_deferred_prepass( + graph, + render_context, + view_query, + true, + world, + "late deferred prepass", + ) + } +} + +/// Runs the deferred prepass that draws all meshes to the depth buffer and +/// G-buffers. +/// +/// If occlusion culling isn't in use, and a prepass is enabled, then there's +/// only one prepass. If occlusion culling is in use, then any prepass is split +/// into two: an *early* prepass and a *late* prepass. The early prepass draws +/// what was visible last frame, and the last prepass performs occlusion culling +/// against a conservative hierarchical Z buffer before drawing unoccluded +/// meshes. +fn run_deferred_prepass<'w>( + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (camera, extracted_view, view_depth_texture, view_prepass_textures, _, _): QueryItem< + 'w, + ::ViewQuery, + >, + is_late: bool, + world: &'w World, + label: &'static str, +) -> Result<(), NodeRunError> { + let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( + world.get_resource::>(), + world.get_resource::>(), + ) else { + return Ok(()); + }; + + let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = ( + opaque_deferred_phases.get(&extracted_view.retained_view_entity), + alpha_mask_deferred_phases.get(&extracted_view.retained_view_entity), + ) else { + return Ok(()); + }; + + let mut color_attachments = vec![]; + color_attachments.push( + view_prepass_textures + .normal + .as_ref() + .map(|normals_texture| normals_texture.get_attachment()), + ); + color_attachments.push( + view_prepass_textures + .motion_vectors + .as_ref() + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), + ); + + // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: + // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. + // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT. + // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9 + // For webgl2 we fallback to manually clearing + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + if !is_late { if let Some(deferred_texture) = &view_prepass_textures.deferred { render_context.command_encoder().clear_texture( &deferred_texture.texture.texture, &bevy_render::render_resource::ImageSubresourceRange::default(), ); } + } - color_attachments.push( - view_prepass_textures - .deferred - .as_ref() - .map(|deferred_texture| { + color_attachments.push( + view_prepass_textures + .deferred + .as_ref() + .map(|deferred_texture| { + if is_late { + deferred_texture.get_attachment() + } else { #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] { bevy_render::render_resource::RenderPassColorAttachment { @@ -105,87 +181,82 @@ impl ViewNode for DeferredGBufferPrepassNode { feature = "webgpu" ))] deferred_texture.get_attachment() - }), - ); - - color_attachments.push( - view_prepass_textures - .deferred_lighting_pass_id - .as_ref() - .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), - ); - - // If all color attachments are none: clear the color attachment list so that no fragment shader is required - if color_attachments.iter().all(Option::is_none) { - color_attachments.clear(); - } + } + }), + ); - let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store)); + color_attachments.push( + view_prepass_textures + .deferred_lighting_pass_id + .as_ref() + .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), + ); + + // If all color attachments are none: clear the color attachment list so that no fragment shader is required + if color_attachments.iter().all(Option::is_none) { + color_attachments.clear(); + } + + let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store)); + + let view_entity = graph.view_entity(); + render_context.add_command_buffer_generation_task(move |render_device| { + #[cfg(feature = "trace")] + let _deferred_span = info_span!("deferred_prepass").entered(); + + // Command encoder setup + let mut command_encoder = render_device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("deferred_prepass_command_encoder"), + }); + + // Render pass setup + let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some(label), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - let view_entity = graph.view_entity(); - render_context.add_command_buffer_generation_task(move |render_device| { + // Opaque draws + if !opaque_deferred_phase.multidrawable_meshes.is_empty() + || !opaque_deferred_phase.batchable_meshes.is_empty() + || !opaque_deferred_phase.unbatchable_meshes.is_empty() + { #[cfg(feature = "trace")] - let _deferred_span = info_span!("deferred_prepass").entered(); - - // Command encoder setup - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("deferred_prepass_command_encoder"), - }); - - // Render pass setup - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("deferred_prepass"), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); + let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered(); + if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the opaque deferred phase {err:?}"); } + } - // Opaque draws - if !opaque_deferred_phase.multidrawable_mesh_keys.is_empty() - || !opaque_deferred_phase.batchable_mesh_keys.is_empty() - || !opaque_deferred_phase.unbatchable_mesh_keys.is_empty() + // Alpha masked draws + if !alpha_mask_deferred_phase.is_empty() { + #[cfg(feature = "trace")] + let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered(); + if let Err(err) = alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity) { - #[cfg(feature = "trace")] - let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered(); - if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) - { - error!("Error encountered while rendering the opaque deferred phase {err:?}"); - } - } - - // Alpha masked draws - if !alpha_mask_deferred_phase.is_empty() { - #[cfg(feature = "trace")] - let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered(); - if let Err(err) = - alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity) - { - error!( - "Error encountered while rendering the alpha mask deferred phase {err:?}" - ); - } + error!("Error encountered while rendering the alpha mask deferred phase {err:?}"); } + } - drop(render_pass); + drop(render_pass); - // After rendering to the view depth texture, copy it to the prepass depth texture - if let Some(prepass_depth_texture) = &view_prepass_textures.depth { - command_encoder.copy_texture_to_texture( - view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.texture.as_image_copy(), - view_prepass_textures.size, - ); - } + // After rendering to the view depth texture, copy it to the prepass depth texture + if let Some(prepass_depth_texture) = &view_prepass_textures.depth { + command_encoder.copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), + view_prepass_textures.size, + ); + } - command_encoder.finish() - }); + command_encoder.finish() + }); - Ok(()) - } + Ok(()) } diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index c703bb0bb19c2..028bc97350f56 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -69,7 +69,7 @@ use crate::{ fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; -const DOF_SHADER_HANDLE: Handle = Handle::weak_from_u128(2031861180739216043); +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; diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/downsample_depth.wgsl b/crates/bevy_core_pipeline/src/experimental/mip_generation/downsample_depth.wgsl index d24afa390dbca..12a4d2b178bff 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/downsample_depth.wgsl +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/downsample_depth.wgsl @@ -1,8 +1,8 @@ #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT -@group(0) @binding(0) var mip_0: array; // Per pixel +@group(0) @binding(0) var mip_0: texture_storage_2d; #else #ifdef MESHLET -@group(0) @binding(0) var mip_0: array; // Per pixel +@group(0) @binding(0) var mip_0: texture_storage_2d; #else // MESHLET #ifdef MULTISAMPLE @group(0) @binding(0) var mip_0: texture_depth_multisampled_2d; @@ -24,7 +24,7 @@ @group(0) @binding(11) var mip_11: texture_storage_2d; @group(0) @binding(12) var mip_12: texture_storage_2d; @group(0) @binding(13) var samplr: sampler; -struct Constants { max_mip_level: u32, view_width: u32 } +struct Constants { max_mip_level: u32 } var constants: Constants; /// Generates a hierarchical depth buffer. @@ -39,7 +39,6 @@ var intermediate_memory: array, 16>; @compute @workgroup_size(256, 1, 1) fn downsample_depth_first( - @builtin(num_workgroups) num_workgroups: vec3u, @builtin(workgroup_id) workgroup_id: vec3u, @builtin(local_invocation_index) local_invocation_index: u32, ) { @@ -309,12 +308,13 @@ fn reduce_load_mip_6(tex: vec2u) -> f32 { } fn load_mip_0(x: u32, y: u32) -> f32 { - let i = y * constants.view_width + x; #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT - return bitcast(u32(mip_0[i] >> 32u)); + let visibility = textureLoad(mip_0, vec2(x, y)).r; + return bitcast(u32(visibility >> 32u)); #else // MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT #ifdef MESHLET - return bitcast(mip_0[i]); + let visibility = textureLoad(mip_0, vec2(x, y)).r; + return bitcast(visibility); #else // MESHLET // Downsample the top level. #ifdef MULTISAMPLE 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 3140408886b6d..f324193983f3c 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -8,13 +8,13 @@ use core::array; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, prelude::{resource_exists, Without}, - query::{QueryItem, With}, + query::{Or, QueryState, With}, resource::Resource, schedule::IntoSystemConfigs as _, system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut}, @@ -22,8 +22,10 @@ use bevy_ecs::{ }; use bevy_math::{uvec2, UVec2, Vec4Swizzles as _}; use bevy_render::{ - experimental::occlusion_culling::OcclusionCulling, - render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, + experimental::occlusion_culling::{ + OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, + }, + render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::{ binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d}, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, @@ -41,17 +43,14 @@ use bevy_render::{ }; use bitflags::bitflags; -use crate::{ - core_3d::{ - graph::{Core3d, Node3d}, - prepare_core_3d_depth_textures, - }, - prepass::{DeferredPrepass, DepthPrepass}, +use crate::core_3d::{ + graph::{Core3d, Node3d}, + prepare_core_3d_depth_textures, }; /// Identifies the `downsample_depth.wgsl` shader. pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle = - Handle::weak_from_u128(3876351454330663524); + weak_handle!("a09a149e-5922-4fa4-9170-3c1a13065364"); /// The maximum number of mip levels that we can produce. /// @@ -81,21 +80,16 @@ impl Plugin for MipGenerationPlugin { render_app .init_resource::>() - .add_render_graph_node::>( - Core3d, - Node3d::EarlyDownsampleDepth, - ) - .add_render_graph_node::>( - Core3d, - Node3d::LateDownsampleDepth, - ) + .add_render_graph_node::(Core3d, Node3d::EarlyDownsampleDepth) + .add_render_graph_node::(Core3d, Node3d::LateDownsampleDepth) .add_render_graph_edges( Core3d, ( Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::EarlyDownsampleDepth, Node3d::LatePrepass, - Node3d::DeferredPrepass, + Node3d::LateDeferredPrepass, ), ) .add_render_graph_edges( @@ -136,7 +130,7 @@ impl Plugin for MipGenerationPlugin { /// /// This runs the single-pass downsampling (SPD) shader with the *min* filter in /// order to generate a series of mipmaps for the Z buffer. The resulting -/// hierarchical Z buffer can be used for occlusion culling. +/// hierarchical Z-buffer can be used for occlusion culling. /// /// There are two instances of this node. The *early* downsample depth pass is /// the first hierarchical Z-buffer stage, which runs after the early prepass @@ -147,79 +141,150 @@ impl Plugin for MipGenerationPlugin { /// of the *next* frame will perform. /// /// This node won't do anything if occlusion culling isn't on. -#[derive(Default)] -pub struct DownsampleDepthNode; - -impl ViewNode for DownsampleDepthNode { - type ViewQuery = ( +pub struct DownsampleDepthNode { + /// The query that we use to find views that need occlusion culling for + /// their Z-buffer. + main_view_query: QueryState<( Read, Read, Read, - ); + Option>, + )>, + /// The query that we use to find shadow maps that need occlusion culling. + shadow_view_query: QueryState<( + Read, + Read, + Read, + )>, +} + +impl FromWorld for DownsampleDepthNode { + fn from_world(world: &mut World) -> Self { + Self { + main_view_query: QueryState::new(world), + shadow_view_query: QueryState::new(world), + } + } +} + +impl Node for DownsampleDepthNode { + fn update(&mut self, world: &mut World) { + self.main_view_query.update_archetypes(world); + self.shadow_view_query.update_archetypes(world); + } fn run<'w>( &self, render_graph_context: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (view_depth_pyramid, view_downsample_depth_bind_group, view_depth_texture): QueryItem< - 'w, - Self::ViewQuery, - >, world: &'w World, ) -> Result<(), NodeRunError> { - // Produce a depth pyramid from the current depth buffer for a single - // view. The resulting depth pyramid can be used for occlusion testing. - - let downsample_depth_pipelines = world.resource::(); - let pipeline_cache = world.resource::(); - - // Despite the name "single-pass downsampling", we actually need two - // passes because of the lack of `coherent` buffers in WGPU/WGSL. - // Between each pass, there's an implicit synchronization barrier. - - // Fetch the appropriate pipeline ID, depending on whether the depth - // buffer is multisampled or not. - let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) = - (if view_depth_texture.texture.sample_count() > 1 { - ( - downsample_depth_pipelines.first_multisample.pipeline_id, - downsample_depth_pipelines.second_multisample.pipeline_id, - ) - } else { - ( - downsample_depth_pipelines.first.pipeline_id, - downsample_depth_pipelines.second.pipeline_id, - ) - }) + let Ok(( + view_depth_pyramid, + view_downsample_depth_bind_group, + view_depth_texture, + maybe_view_light_entities, + )) = self + .main_view_query + .get_manual(world, render_graph_context.view_entity()) else { return Ok(()); }; - // Fetch the pipelines for the two passes. - let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = ( - pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id), - pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id), - ) else { - return Ok(()); - }; - - // Run the depth downsampling. - let view_size = uvec2( - view_depth_texture.texture.width(), - view_depth_texture.texture.height(), - ); - view_depth_pyramid.downsample_depth( - &format!("{:?}", render_graph_context.label()), + // Downsample depth for the main Z-buffer. + downsample_depth( + render_graph_context, render_context, - view_size, + world, + view_depth_pyramid, view_downsample_depth_bind_group, - first_downsample_depth_pipeline, - second_downsample_depth_pipeline, - ); + uvec2( + view_depth_texture.texture.width(), + view_depth_texture.texture.height(), + ), + view_depth_texture.texture.sample_count(), + )?; + + // Downsample depth for shadow maps that have occlusion culling enabled. + if let Some(view_light_entities) = maybe_view_light_entities { + for &view_light_entity in &view_light_entities.0 { + let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) = + self.shadow_view_query.get_manual(world, view_light_entity) + else { + continue; + }; + downsample_depth( + render_graph_context, + render_context, + world, + view_depth_pyramid, + view_downsample_depth_bind_group, + UVec2::splat(occlusion_culling.depth_texture_size), + 1, + )?; + } + } + Ok(()) } } +/// Produces a depth pyramid from the current depth buffer for a single view. +/// The resulting depth pyramid can be used for occlusion testing. +fn downsample_depth<'w>( + render_graph_context: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + view_depth_pyramid: &ViewDepthPyramid, + view_downsample_depth_bind_group: &ViewDownsampleDepthBindGroup, + view_size: UVec2, + sample_count: u32, +) -> Result<(), NodeRunError> { + let downsample_depth_pipelines = world.resource::(); + let pipeline_cache = world.resource::(); + + // Despite the name "single-pass downsampling", we actually need two + // passes because of the lack of `coherent` buffers in WGPU/WGSL. + // Between each pass, there's an implicit synchronization barrier. + + // Fetch the appropriate pipeline ID, depending on whether the depth + // buffer is multisampled or not. + let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) = + (if sample_count > 1 { + ( + downsample_depth_pipelines.first_multisample.pipeline_id, + downsample_depth_pipelines.second_multisample.pipeline_id, + ) + } else { + ( + downsample_depth_pipelines.first.pipeline_id, + downsample_depth_pipelines.second.pipeline_id, + ) + }) + else { + return Ok(()); + }; + + // Fetch the pipelines for the two passes. + let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = ( + pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id), + pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id), + ) else { + return Ok(()); + }; + + // Run the depth downsampling. + view_depth_pyramid.downsample_depth( + &format!("{:?}", render_graph_context.label()), + render_context, + view_size, + view_downsample_depth_bind_group, + first_downsample_depth_pipeline, + second_downsample_depth_pipeline, + ); + Ok(()) +} + /// A single depth downsample pipeline. #[derive(Resource)] pub struct DownsampleDepthPipeline { @@ -427,7 +492,7 @@ impl SpecializedComputePipeline for DownsampleDepthPipeline { layout: vec![self.bind_group_layout.clone()], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, - range: 0..8, + range: 0..4, }], shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader_defs, @@ -484,6 +549,7 @@ pub fn create_depth_pyramid_dummy_texture( label: Some(texture_view_label), format: Some(TextureFormat::R32Float), dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::All, base_mip_level: 0, mip_level_count: Some(1), @@ -551,6 +617,7 @@ impl ViewDepthPyramid { label: Some(texture_view_label), format: Some(TextureFormat::R32Float), dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::All, base_mip_level: i as u32, mip_level_count: Some(1), @@ -625,9 +692,8 @@ impl ViewDepthPyramid { timestamp_writes: None, }); downsample_pass.set_pipeline(downsample_depth_first_pipeline); - // Pass the mip count and the texture width as push constants, for - // simplicity. - downsample_pass.set_push_constants(0, bytemuck::cast_slice(&[self.mip_count, view_size.x])); + // Pass the mip count as a push constant, for simplicity. + downsample_pass.set_push_constants(0, &self.mip_count.to_le_bytes()); downsample_pass.set_bind_group(0, downsample_depth_bind_group, &[]); downsample_pass.dispatch_workgroups(view_size.x.div_ceil(64), view_size.y.div_ceil(64), 1); @@ -639,20 +705,12 @@ impl ViewDepthPyramid { } /// Creates depth pyramids for views that have occlusion culling enabled. -fn prepare_view_depth_pyramids( +pub fn prepare_view_depth_pyramids( mut commands: Commands, render_device: Res, mut texture_cache: ResMut, depth_pyramid_dummy_texture: Res, - views: Query< - (Entity, &ExtractedView), - ( - With, - Without, - With, - Without, - ), - >, + views: Query<(Entity, &ExtractedView), (With, Without)>, ) { for (view_entity, view) in &views { commands.entity(view_entity).insert(ViewDepthPyramid::new( @@ -679,10 +737,21 @@ fn prepare_downsample_depth_view_bind_groups( mut commands: Commands, render_device: Res, downsample_depth_pipelines: Res, - view_depth_textures: Query<(Entity, &ViewDepthPyramid, &ViewDepthTexture)>, + view_depth_textures: Query< + ( + Entity, + &ViewDepthPyramid, + Option<&ViewDepthTexture>, + Option<&OcclusionCullingSubview>, + ), + Or<(With, With)>, + >, ) { - for (view_entity, view_depth_pyramid, view_depth_texture) in &view_depth_textures { - let is_multisampled = view_depth_texture.texture.sample_count() > 1; + for (view_entity, view_depth_pyramid, view_depth_texture, shadow_occlusion_culling) in + &view_depth_textures + { + let is_multisampled = view_depth_texture + .is_some_and(|view_depth_texture| view_depth_texture.texture.sample_count() > 1); commands .entity(view_entity) .insert(ViewDownsampleDepthBindGroup( @@ -700,7 +769,13 @@ fn prepare_downsample_depth_view_bind_groups( } else { &downsample_depth_pipelines.first.bind_group_layout }, - view_depth_texture.view(), + match (view_depth_texture, shadow_occlusion_culling) { + (Some(view_depth_texture), _) => view_depth_texture.view(), + (None, Some(shadow_occlusion_culling)) => { + &shadow_occlusion_culling.depth_texture_view + } + (None, None) => panic!("Should never happen"), + }, &downsample_depth_pipelines.sampler, ), )); diff --git a/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs index d01c34477503d..fee17d1ec6412 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs @@ -1,7 +1,8 @@ -use bevy_asset::Handle; +use bevy_asset::{weak_handle, Handle}; use bevy_render::{prelude::Shader, render_resource::VertexState}; -pub const FULLSCREEN_SHADER_HANDLE: Handle = Handle::weak_from_u128(7837534426033940724); +pub const FULLSCREEN_SHADER_HANDLE: Handle = + weak_handle!("481fb759-d0b1-4175-8319-c439acde30a2"); /// uses the [`FULLSCREEN_SHADER_HANDLE`] to output a /// ```wgsl diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 547b59762442f..b8904bb1f9ffc 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -4,7 +4,7 @@ use crate::{ fullscreen_vertex_shader::fullscreen_shader_vertex_state, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Handle}; +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}; @@ -80,7 +80,7 @@ impl Default for Fxaa { } } -const FXAA_SHADER_HANDLE: Handle = Handle::weak_from_u128(4182761465141723543); +const FXAA_SHADER_HANDLE: Handle = weak_handle!("fc58c0a8-01c0-46e9-94cc-83a794bae7b0"); /// Adds support for Fast Approximate Anti-Aliasing (FXAA) pub struct FxaaPlugin; diff --git a/crates/bevy_core_pipeline/src/motion_blur/mod.rs b/crates/bevy_core_pipeline/src/motion_blur/mod.rs index e4839b25b44c9..7703698f1ab10 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/mod.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/mod.rs @@ -7,7 +7,7 @@ use crate::{ prepass::{DepthPrepass, MotionVectorPrepass}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{ component::{require, Component}, query::With, @@ -108,7 +108,7 @@ impl Default for MotionBlur { } pub const MOTION_BLUR_SHADER_HANDLE: Handle = - Handle::weak_from_u128(987457899187986082347921); + weak_handle!("d9ca74af-fa0a-4f11-b0f2-19613b618b93"); /// Adds support for per-object motion blur to the app. See [`MotionBlur`] for details. pub struct MotionBlurPlugin; diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index a0ac81646d84d..63083f5ed7f77 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -1,7 +1,7 @@ //! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details. use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{component::*, prelude::*}; use bevy_math::UVec2; use bevy_platform_support::collections::HashSet; @@ -34,7 +34,8 @@ use crate::core_3d::{ 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 = Handle::weak_from_u128(4042527984320512); +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. @@ -69,17 +70,17 @@ impl Component for OrderIndependentTransparencySettings { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|world, context| { + fn on_add() -> Option { + Some(|world, context| { if let Some(value) = world.get::(context.entity) { if value.layer_count > 32 { - warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", + warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", context.caller.map(|location|format!("{location}: ")).unwrap_or_default(), value.layer_count ); } } - }); + }) } } diff --git a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs index 690f95fda33db..f73192b19dec9 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs @@ -3,7 +3,7 @@ use crate::{ oit::OrderIndependentTransparencySettings, }; use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::Deref; use bevy_ecs::{ entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, @@ -27,7 +27,8 @@ 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 = Handle::weak_from_u128(7698420424769536); +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; diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 2873f5cb7754f..c8b1da4497363 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, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -47,17 +47,18 @@ use crate::{ }; /// The handle to the built-in postprocessing shader `post_process.wgsl`. -const POST_PROCESSING_SHADER_HANDLE: Handle = Handle::weak_from_u128(14675654334038973533); +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 = - Handle::weak_from_u128(10969893303667163833); + 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 /// one blue pixel, in that order. const DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE: Handle = - Handle::weak_from_u128(2199972955136579180); + weak_handle!("dc3e3307-40a1-49bb-be6d-e0634e8836b2"); /// The default chromatic aberration intensity amount, in a fraction of the /// window size. diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 942983b06309a..04cc1890b0235 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -66,6 +66,7 @@ impl ViewNode for LatePrepassNode { Option<&'static PreviousViewUniformOffset>, Has, Has, + Has, ); fn run<'w>( @@ -77,7 +78,7 @@ impl ViewNode for LatePrepassNode { ) -> Result<(), NodeRunError> { // We only need a late prepass if we have occlusion culling and indirect // drawing. - let (_, _, _, _, _, _, _, _, _, occlusion_culling, no_indirect_drawing) = query; + let (_, _, _, _, _, _, _, _, _, occlusion_culling, no_indirect_drawing, _) = query; if !occlusion_culling || no_indirect_drawing { return Ok(()); } @@ -110,10 +111,18 @@ fn run_prepass<'w>( view_prev_uniform_offset, _, _, + has_deferred, ): QueryItem<'w, ::ViewQuery>, world: &'w World, label: &'static str, ) -> Result<(), NodeRunError> { + // If we're using deferred rendering, there will be a deferred prepass + // instead of this one. Just bail out so we don't have to bother looking at + // the empty bins. + if has_deferred { + return Ok(()); + } + let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = ( world.get_resource::>(), world.get_resource::>(), diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index 6799af2d52c6d..6e1a8422768a8 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -1,14 +1,16 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_ecs::{ prelude::{Component, Entity}, query::{QueryItem, With}, + reflect::ReflectComponent, resource::Resource, schedule::IntoSystemConfigs, system::{Commands, Query, Res, ResMut}, }; use bevy_image::{BevyDefault, Image}; use bevy_math::{Mat4, Quat}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::Exposure, extract_component::{ @@ -30,7 +32,7 @@ use prepass::{SkyboxPrepassPipeline, SKYBOX_PREPASS_SHADER_HANDLE}; use crate::{core_3d::CORE_3D_DEPTH_FORMAT, prepass::PreviousViewUniforms}; -const SKYBOX_SHADER_HANDLE: Handle = Handle::weak_from_u128(55594763423201); +const SKYBOX_SHADER_HANDLE: Handle = weak_handle!("a66cf9cc-cab8-47f8-ac32-db82fdc4f29b"); pub mod prepass; @@ -46,7 +48,7 @@ impl Plugin for SkyboxPlugin { Shader::from_wgsl ); - app.add_plugins(( + app.register_type::().add_plugins(( ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), )); @@ -87,7 +89,8 @@ impl Plugin for SkyboxPlugin { /// To do so, use `EnvironmentMapLight` alongside this component. /// /// See also . -#[derive(Component, Clone)] +#[derive(Component, Clone, Reflect)] +#[reflect(Component, Default)] pub struct Skybox { pub image: Handle, /// Scale factor applied to the skybox image. diff --git a/crates/bevy_core_pipeline/src/skybox/prepass.rs b/crates/bevy_core_pipeline/src/skybox/prepass.rs index ae7fcd325e7d1..658660bbc62ff 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::Handle; +use bevy_asset::{weak_handle, Handle}; use bevy_ecs::{ component::Component, entity::Entity, @@ -30,7 +30,8 @@ use crate::{ Skybox, }; -pub const SKYBOX_PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(376510055324461154); +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. /// diff --git a/crates/bevy_core_pipeline/src/smaa/mod.rs b/crates/bevy_core_pipeline/src/smaa/mod.rs index 3ab4ff1aec7e5..fb066a3ce538c 100644 --- a/crates/bevy_core_pipeline/src/smaa/mod.rs +++ b/crates/bevy_core_pipeline/src/smaa/mod.rs @@ -38,7 +38,7 @@ use crate::{ use bevy_app::{App, Plugin}; #[cfg(feature = "smaa_luts")] use bevy_asset::load_internal_binary_asset; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -81,11 +81,13 @@ use bevy_render::{ use bevy_utils::prelude::default; /// The handle of the `smaa.wgsl` shader. -const SMAA_SHADER_HANDLE: Handle = Handle::weak_from_u128(12247928498010601081); +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 = Handle::weak_from_u128(15283551734567401670); +const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle = + weak_handle!("569c4d67-c7fa-4958-b1af-0836023603c0"); /// The handle of the search LUT, a KTX2 format texture that SMAA uses internally. -const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle = Handle::weak_from_u128(3187314362190283210); +const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle = + weak_handle!("43b97515-252e-4c8a-b9af-f2fc528a1c27"); /// Adds support for subpixel morphological antialiasing, or SMAA. pub struct SmaaPlugin; diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 8e7c3a0c2a138..55eb25ae02ebf 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -5,7 +5,7 @@ use crate::{ prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_diagnostic::FrameCount; use bevy_ecs::{ prelude::{require, Component, Entity, ReflectComponent}, @@ -40,7 +40,7 @@ use bevy_render::{ }; use tracing::warn; -const TAA_SHADER_HANDLE: Handle = Handle::weak_from_u128(656865235226276); +const TAA_SHADER_HANDLE: Handle = weak_handle!("fea20d50-86b6-4069-aa32-374346aec00c"); /// Plugin for temporal anti-aliasing. /// diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index e932639b7f420..443b81327a8e6 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, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Assets, Handle}; use bevy_ecs::prelude::*; use bevy_image::{CompressedImageFormats, Image, ImageSampler, ImageType}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -27,13 +27,14 @@ mod node; use bevy_utils::default; pub use node::TonemappingNode; -const TONEMAPPING_SHADER_HANDLE: Handle = Handle::weak_from_u128(17015368199668024512); +const TONEMAPPING_SHADER_HANDLE: Handle = + weak_handle!("e239c010-c25c-42a1-b4e8-08818764d667"); const TONEMAPPING_SHARED_SHADER_HANDLE: Handle = - Handle::weak_from_u128(2499430578245347910); + weak_handle!("61dbc544-4b30-4ca9-83bd-4751b5cfb1b1"); const TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(8392056472189465073); + weak_handle!("d50e3a70-c85e-4725-a81e-72fc83281145"); /// 3D LUT (look up table) textures used for tonemapping #[derive(Resource, Clone, ExtractResource)] @@ -464,7 +465,7 @@ pub fn lut_placeholder() -> Image { let format = TextureFormat::Rgba8Unorm; let data = vec![255, 0, 255, 255]; Image { - data, + data: Some(data), texture_descriptor: TextureDescriptor { size: Extent3d { width: 1, diff --git a/crates/bevy_derive/LICENSE-APACHE b/crates/bevy_derive/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_derive/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_derive/LICENSE-MIT b/crates/bevy_derive/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_derive/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_dev_tools/LICENSE-APACHE b/crates/bevy_dev_tools/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_dev_tools/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_dev_tools/LICENSE-MIT b/crates/bevy_dev_tools/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_dev_tools/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_diagnostic/LICENSE-APACHE b/crates/bevy_diagnostic/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_diagnostic/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_diagnostic/LICENSE-MIT b/crates/bevy_diagnostic/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_diagnostic/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_dylib/LICENSE-APACHE b/crates/bevy_dylib/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_dylib/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_dylib/LICENSE-MIT b/crates/bevy_dylib/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_dylib/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_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 0106286c52d55..bd3d02d0308eb 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -103,7 +103,9 @@ portable-atomic = [ [dependencies] bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" } -bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } +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, optional = true } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [ "alloc", diff --git a/crates/bevy_ecs/LICENSE-APACHE b/crates/bevy_ecs/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_ecs/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_ecs/LICENSE-MIT b/crates/bevy_ecs/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_ecs/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_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.rs b/crates/bevy_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.rs new file mode 100644 index 0000000000000..462e9dbf1f056 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.rs @@ -0,0 +1,24 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::system::SystemState; + +#[derive(Component, Eq, PartialEq, Debug)] +struct Foo(u32); + +fn main() { + let mut world = World::default(); + let e = world.spawn(Foo(10_u32)).id(); + + let mut system_state = SystemState::>::new(&mut world); + { + let mut query = system_state.get_mut(&mut world); + let mut lens = query.as_query_lens(); + dbg!("hi"); + { + let mut data: Mut = lens.query().get_inner(e).unwrap(); + let mut data2: Mut = lens.query().get_inner(e).unwrap(); + //~^ E0499 + assert_eq!(&mut *data, &mut *data2); // oops UB + } + dbg!("bye"); + } +} diff --git a/crates/bevy_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.stderr b/crates/bevy_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.stderr new file mode 100644 index 0000000000000..060b32029556f --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/ui/query_lens_lifetime_safety.stderr @@ -0,0 +1,5 @@ +error[E0499]: cannot borrow `lens` as mutable more than once at a time + --> tests/ui\query_lens_lifetime_safety.rs:18:39 + | +17 | let mut data: Mut = lens.query().get_inner(e).unwrap(); + | ---- first mutable borrow occurs here diff --git a/crates/bevy_ecs/compile_fail/tests/ui/query_to_readonly.rs b/crates/bevy_ecs/compile_fail/tests/ui/query_to_readonly.rs index 7e5a23cb2e361..e8c66cff300b9 100644 --- a/crates/bevy_ecs/compile_fail/tests/ui/query_to_readonly.rs +++ b/crates/bevy_ecs/compile_fail/tests/ui/query_to_readonly.rs @@ -6,29 +6,29 @@ struct Foo; fn for_loops(mut query: Query<&mut Foo>) { // this should fail to compile for _ in query.iter_mut() { - for _ in query.to_readonly().iter() {} + for _ in query.as_readonly().iter() {} //~^ E0502 } // this should fail to compile - for _ in query.to_readonly().iter() { + for _ in query.as_readonly().iter() { for _ in query.iter_mut() {} //~^ E0502 } // this should *not* fail to compile - for _ in query.to_readonly().iter() { - for _ in query.to_readonly().iter() {} + for _ in query.as_readonly().iter() { + for _ in query.as_readonly().iter() {} } // this should *not* fail to compile - for _ in query.to_readonly().iter() { + for _ in query.as_readonly().iter() { for _ in query.iter() {} } // this should *not* fail to compile for _ in query.iter() { - for _ in query.to_readonly().iter() {} + for _ in query.as_readonly().iter() {} } } @@ -38,11 +38,11 @@ fn single_mut_query(mut query: Query<&mut Foo>) { let mut mut_foo = query.single_mut(); // This solves "temporary value dropped while borrowed" - let readonly_query = query.to_readonly(); + let readonly_query = query.as_readonly(); //~^ E0502 let ref_foo = readonly_query.single(); - + *mut_foo = Foo; println!("{ref_foo:?}"); @@ -51,7 +51,7 @@ fn single_mut_query(mut query: Query<&mut Foo>) { // this should fail to compile { // This solves "temporary value dropped while borrowed" - let readonly_query = query.to_readonly(); + let readonly_query = query.as_readonly(); let ref_foo = readonly_query.single(); @@ -66,7 +66,7 @@ fn single_mut_query(mut query: Query<&mut Foo>) { // this should *not* fail to compile { // This solves "temporary value dropped while borrowed" - let readonly_query = query.to_readonly(); + let readonly_query = query.as_readonly(); let readonly_foo = readonly_query.single(); diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index aac9dc38bc860..711ba57e1af88 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -45,7 +45,7 @@ struct MyEvent { fn sending_system(mut event_writer: EventWriter) { let random_value: f32 = rand::random(); if random_value > 0.5 { - event_writer.send(MyEvent { + event_writer.write(MyEvent { message: "A random event with value > 0.5".to_string(), random_value, }); diff --git a/crates/bevy_ecs/macros/LICENSE-APACHE b/crates/bevy_ecs/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_ecs/macros/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_ecs/macros/LICENSE-MIT b/crates/bevy_ecs/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_ecs/macros/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_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index ba695bef49550..76732c0b895ac 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -1,6 +1,6 @@ use proc_macro::{TokenStream, TokenTree}; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote, ToTokens}; use std::collections::HashSet; use syn::{ parenthesized, @@ -9,8 +9,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, LitStr, Path, Result, - Token, Visibility, + Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, Index, LitStr, Member, + Path, Result, Token, Visibility, }; pub fn derive_event(input: TokenStream) -> TokenStream { @@ -51,6 +51,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { }) } +const ENTITIES_ATTR: &str = "entities"; + pub fn derive_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); @@ -69,17 +71,15 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Err(err) => err.into_compile_error().into(), }; + let visit_entities = visit_entities(&ast.data, &bevy_ecs_path, relationship.is_some()); + let storage = storage_path(&bevy_ecs_path, attrs.storage); - let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add); - let mut on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); - let mut on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); - let on_remove: Option = - hook_register_function_call(quote! {on_remove}, attrs.on_remove); - let mut on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); + let on_add_path = attrs.on_add.map(|path| path.to_token_stream()); + let on_remove_path = attrs.on_remove.map(|path| path.to_token_stream()); - if relationship.is_some() { - if on_insert.is_some() { + let on_insert_path = if relationship.is_some() { + if attrs.on_insert.is_some() { return syn::Error::new( ast.span(), "Custom on_insert hooks are not supported as relationships already define an on_insert hook", @@ -88,11 +88,13 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_insert = Some( - quote!(hooks.on_insert(::on_insert);), - ); + Some(quote!(::on_insert)) + } else { + attrs.on_insert.map(|path| path.to_token_stream()) + }; - if on_replace.is_some() { + let on_replace_path = if relationship.is_some() { + if attrs.on_replace.is_some() { return syn::Error::new( ast.span(), "Custom on_replace hooks are not supported as Relationships already define an on_replace hook", @@ -101,13 +103,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_replace = Some( - quote!(hooks.on_replace(::on_replace);), - ); - } - - if let Some(relationship_target) = &attrs.relationship_target { - if on_replace.is_some() { + Some(quote!(::on_replace)) + } else if attrs.relationship_target.is_some() { + if attrs.on_replace.is_some() { return syn::Error::new( ast.span(), "Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook", @@ -116,25 +114,36 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_replace = Some( - quote!(hooks.on_replace(::on_replace);), - ); - - if relationship_target.despawn_descendants { - if on_despawn.is_some() { - return syn::Error::new( - ast.span(), - "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", - ) - .into_compile_error() - .into(); - } + Some(quote!(::on_replace)) + } else { + attrs.on_replace.map(|path| path.to_token_stream()) + }; - on_despawn = Some( - quote!(hooks.on_despawn(::on_despawn);), - ); + let on_despawn_path = if attrs + .relationship_target + .is_some_and(|target| target.linked_spawn) + { + if attrs.on_despawn.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute", + ) + .into_compile_error() + .into(); } - } + + Some(quote!(::on_despawn)) + } else { + attrs.on_despawn.map(|path| path.to_token_stream()) + }; + + let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path); + let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path); + let on_replace = + hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path); + let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path); + let on_despawn = + hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path); ast.generics .make_where_clause() @@ -151,7 +160,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { <#ident as #bevy_ecs_path::component::Component>::register_required_components( requiree, components, - storages, required_components, inheritance_depth + 1, recursion_check_stack @@ -161,7 +169,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Some(RequireFunc::Path(func)) => { register_required.push(quote! { components.register_required_components_manual::( - storages, required_components, || { let x: #ident = #func().into(); x }, inheritance_depth, @@ -172,7 +179,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { Some(RequireFunc::Closure(func)) => { register_required.push(quote! { components.register_required_components_manual::( - storages, required_components, || { let x: #ident = (#func)().into(); x }, inheritance_depth, @@ -183,7 +189,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { None => { register_required.push(quote! { components.register_required_components_manual::( - storages, required_components, <#ident as Default>::default, inheritance_depth, @@ -201,12 +206,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .then_some(quote! { #bevy_ecs_path::component::Immutable }) .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); - let clone_handler = if relationship_target.is_some() { - quote!(#bevy_ecs_path::component::ComponentCloneHandler::ignore()) + let clone_behavior = if relationship_target.is_some() { + quote!(#bevy_ecs_path::component::ComponentCloneBehavior::RelationshipTarget(#bevy_ecs_path::relationship::clone_relationship_target::)) } else { quote!( - use #bevy_ecs_path::component::{ComponentCloneViaClone, ComponentCloneBase}; - (&&&#bevy_ecs_path::component::ComponentCloneSpecializationWrapper::::default()).get_component_clone_handler() + use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone}; + (&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::::default()).default_clone_behavior() ) }; @@ -219,31 +224,29 @@ pub fn derive_component(input: TokenStream) -> TokenStream { fn register_required_components( requiree: #bevy_ecs_path::component::ComponentId, components: &mut #bevy_ecs_path::component::Components, - storages: &mut #bevy_ecs_path::storage::Storages, required_components: &mut #bevy_ecs_path::component::RequiredComponents, inheritance_depth: u16, recursion_check_stack: &mut #bevy_ecs_path::__macro_exports::Vec<#bevy_ecs_path::component::ComponentId> ) { #bevy_ecs_path::component::enforce_no_required_components_recursion(components, recursion_check_stack); - let self_id = components.register_component::(storages); + let self_id = components.register_component::(); recursion_check_stack.push(self_id); #(#register_required)* #(#register_recursive_requires)* recursion_check_stack.pop(); } - #[allow(unused_variables)] - fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { - #on_add - #on_insert - #on_replace - #on_remove - #on_despawn - } + #on_add + #on_insert + #on_replace + #on_remove + #on_despawn - fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { - #clone_handler + fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior { + #clone_behavior } + + #visit_entities } #relationship @@ -252,6 +255,144 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }) } +fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> TokenStream2 { + match data { + Data::Struct(DataStruct { ref fields, .. }) => { + let mut visited_fields = Vec::new(); + let mut visited_indices = Vec::new(); + match fields { + Fields::Named(fields) => { + for field in &fields.named { + if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + if let Some(ident) = field.ident.clone() { + visited_fields.push(ident); + } + } + } + } + Fields::Unnamed(fields) => { + for (index, field) in fields.unnamed.iter().enumerate() { + if index == 0 && is_relationship { + visited_indices.push(Index::from(0)); + } else if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + visited_indices.push(Index::from(index)); + } + } + } + Fields::Unit => {} + } + + if visited_fields.is_empty() && visited_indices.is_empty() { + TokenStream2::new() + } else { + let visit = visited_fields + .iter() + .map(|field| quote!(this.#field.visit_entities(&mut func);)) + .chain( + visited_indices + .iter() + .map(|index| quote!(this.#index.visit_entities(&mut func);)), + ); + let visit_mut = visited_fields + .iter() + .map(|field| quote!(this.#field.visit_entities_mut(&mut func);)) + .chain( + visited_indices + .iter() + .map(|index| quote!(this.#index.visit_entities_mut(&mut func);)), + ); + quote!( + fn visit_entities(this: &Self, mut func: impl FnMut(Entity)) { + use #bevy_ecs_path::entity::VisitEntities; + #(#visit)* + } + + fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut Entity)) { + use #bevy_ecs_path::entity::VisitEntitiesMut; + #(#visit_mut)* + } + ) + } + } + Data::Enum(data_enum) => { + let mut has_visited_fields = false; + let mut visit_variants = Vec::with_capacity(data_enum.variants.len()); + let mut visit_variants_mut = Vec::with_capacity(data_enum.variants.len()); + for variant in &data_enum.variants { + let mut variant_fields = Vec::new(); + let mut variant_fields_mut = Vec::new(); + + let mut visit_variant_fields = Vec::new(); + let mut visit_variant_fields_mut = Vec::new(); + + for (index, field) in variant.fields.iter().enumerate() { + if field + .attrs + .iter() + .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) + { + has_visited_fields = true; + let field_member = ident_or_index(field.ident.as_ref(), index); + let field_ident = format_ident!("field_{}", field_member); + + variant_fields.push(quote!(#field_member: ref #field_ident)); + variant_fields_mut.push(quote!(#field_member: ref mut #field_ident)); + + visit_variant_fields.push(quote!(#field_ident.visit_entities(&mut func);)); + visit_variant_fields_mut + .push(quote!(#field_ident.visit_entities_mut(&mut func);)); + } + } + + let ident = &variant.ident; + visit_variants.push(quote!(Self::#ident {#(#variant_fields,)* ..} => { + #(#visit_variant_fields)* + })); + visit_variants_mut.push(quote!(Self::#ident {#(#variant_fields_mut,)* ..} => { + #(#visit_variant_fields_mut)* + })); + } + if has_visited_fields { + quote!( + fn visit_entities(this: &Self, mut func: impl FnMut(Entity)) { + use #bevy_ecs_path::entity::VisitEntities; + match this { + #(#visit_variants,)* + _ => {} + } + } + + fn visit_entities_mut(this: &mut Self, mut func: impl FnMut(&mut Entity)) { + use #bevy_ecs_path::entity::VisitEntitiesMut; + match this { + #(#visit_variants_mut,)* + _ => {} + } + } + ) + } else { + TokenStream2::new() + } + } + Data::Union(_) => TokenStream2::new(), + } +} + +pub(crate) fn ident_or_index(ident: Option<&Ident>, index: usize) -> Member { + ident.map_or_else( + || Member::Unnamed(index.into()), + |ident| Member::Named(ident.clone()), + ) +} + pub fn document_required_components(attr: TokenStream, item: TokenStream) -> TokenStream { let paths = parse_macro_input!(attr with Punctuated::::parse_terminated) .iter() @@ -329,7 +470,7 @@ struct Relationship { struct RelationshipTarget { relationship: Ident, - despawn_descendants: bool, + linked_spawn: bool, } // values for `storage` attribute @@ -444,10 +585,17 @@ fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { } fn hook_register_function_call( + bevy_ecs_path: &Path, hook: TokenStream2, - function: Option, + function: Option, ) -> Option { - function.map(|meta| quote! { hooks. #hook (#meta); }) + function.map(|meta| { + quote! { + fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> { + ::core::option::Option::Some(#meta) + } + } + }) } impl Parse for Relationship { @@ -464,18 +612,18 @@ impl Parse for Relationship { impl Parse for RelationshipTarget { fn parse(input: syn::parse::ParseStream) -> Result { let mut relationship_ident = None; - let mut despawn_descendants_exists = false; + let mut linked_spawn_exists = false; syn::custom_keyword!(relationship); - syn::custom_keyword!(despawn_descendants); + syn::custom_keyword!(linked_spawn); let mut done = false; loop { if input.peek(relationship) { input.parse::()?; input.parse::()?; relationship_ident = Some(input.parse::()?); - } else if input.peek(despawn_descendants) { - input.parse::()?; - despawn_descendants_exists = true; + } else if input.peek(linked_spawn) { + input.parse::()?; + linked_spawn_exists = true; } else { done = true; } @@ -490,7 +638,7 @@ impl Parse for RelationshipTarget { let relationship = relationship_ident.ok_or_else(|| syn::Error::new(input.span(), "RelationshipTarget derive must specify a relationship via #[relationship_target(relationship = X)"))?; Ok(RelationshipTarget { relationship, - despawn_descendants: despawn_descendants_exists, + linked_spawn: linked_spawn_exists, }) } } @@ -582,8 +730,10 @@ fn derive_relationship_target( let relationship = &relationship_target.relationship; let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let linked_spawn = relationship_target.linked_spawn; Ok(Some(quote! { impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause { + const LINKED_SPAWN: bool = #linked_spawn; type Relationship = #relationship; type Collection = #collection; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index e4cca41a4d7aa..f61889651df1e 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -86,10 +86,10 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { match field_kind { BundleFieldKind::Component => { field_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); + <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids); }); field_required_components.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, storages, required_components); + <#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, required_components); }); field_get_component_ids.push(quote! { <#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids); @@ -100,7 +100,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { self.#field.get_components(&mut *func); }); field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), + #field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), }); } None => { @@ -109,7 +109,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { self.#index.get_components(&mut *func); }); field_from_components.push(quote! { - #index: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), + #index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), }); } } @@ -128,13 +128,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { TokenStream::from(quote! { // SAFETY: - // - ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order + // - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass // the correct `StorageType` into the callback. + #[allow(deprecated)] unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { fn component_ids( components: &mut #ecs_path::component::Components, - storages: &mut #ecs_path::storage::Storages, ids: &mut impl FnMut(#ecs_path::component::ComponentId) ){ #(#field_component_ids)* @@ -147,6 +147,18 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { #(#field_get_component_ids)* } + fn register_required_components( + components: &mut #ecs_path::component::Components, + required_components: &mut #ecs_path::component::RequiredComponents + ){ + #(#field_required_components)* + } + } + + // SAFETY: + // - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order + #[allow(deprecated)] + unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause { #[allow(unused_variables, non_snake_case)] unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self where @@ -156,17 +168,11 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { #(#field_from_components)* } } - - fn register_required_components( - components: &mut #ecs_path::component::Components, - storages: &mut #ecs_path::storage::Storages, - required_components: &mut #ecs_path::component::RequiredComponents - ){ - #(#field_required_components)* - } } + #[allow(deprecated)] impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause { + type Effect = (); #[allow(unused_variables)] #[inline] fn get_components( @@ -589,7 +595,10 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } -#[proc_macro_derive(Component, attributes(component, relationship, relationship_target))] +#[proc_macro_derive( + Component, + attributes(component, relationship, relationship_target, entities) +)] pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } diff --git a/crates/bevy_ecs/macros/src/query_data.rs b/crates/bevy_ecs/macros/src/query_data.rs index 972f7e33b8ae0..ffac58ef1d313 100644 --- a/crates/bevy_ecs/macros/src/query_data.rs +++ b/crates/bevy_ecs/macros/src/query_data.rs @@ -176,12 +176,10 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &path, &struct_name, &visibility, - &item_struct_name, &fetch_struct_name, &field_types, &user_impl_generics, &user_impl_generics_with_world, - &field_idents, &user_ty_generics, &user_ty_generics_with_world, &named_field_idents, @@ -213,12 +211,10 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { &path, &read_only_struct_name, &visibility, - &read_only_item_struct_name, &read_only_fetch_struct_name, &read_only_field_types, &user_impl_generics, &user_impl_generics_with_world, - &field_idents, &user_ty_generics, &user_ty_generics_with_world, &named_field_idents, @@ -259,6 +255,29 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { unsafe impl #user_impl_generics #path::query::QueryData for #read_only_struct_name #user_ty_generics #user_where_clauses { type ReadOnly = #read_only_struct_name #user_ty_generics; + type Item<'__w> = #read_only_item_struct_name #user_ty_generics_with_world; + + fn shrink<'__wlong: '__wshort, '__wshort>( + item: Self::Item<'__wlong> + ) -> Self::Item<'__wshort> { + #read_only_item_struct_name { + #( + #field_idents: <#read_only_field_types>::shrink(item.#field_idents), + )* + } + } + + /// SAFETY: we call `fetch` for each member that implements `Fetch`. + #[inline(always)] + unsafe fn fetch<'__w>( + _fetch: &mut ::Fetch<'__w>, + _entity: #path::entity::Entity, + _table_row: #path::storage::TableRow, + ) -> Self::Item<'__w> { + Self::Item { + #(#field_idents: <#read_only_field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + } + } } } } else { @@ -270,6 +289,29 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream { unsafe impl #user_impl_generics #path::query::QueryData for #struct_name #user_ty_generics #user_where_clauses { type ReadOnly = #read_only_struct_name #user_ty_generics; + type Item<'__w> = #item_struct_name #user_ty_generics_with_world; + + fn shrink<'__wlong: '__wshort, '__wshort>( + item: Self::Item<'__wlong> + ) -> Self::Item<'__wshort> { + #item_struct_name { + #( + #field_idents: <#field_types>::shrink(item.#field_idents), + )* + } + } + + /// SAFETY: we call `fetch` for each member that implements `Fetch`. + #[inline(always)] + unsafe fn fetch<'__w>( + _fetch: &mut ::Fetch<'__w>, + _entity: #path::entity::Entity, + _table_row: #path::storage::TableRow, + ) -> Self::Item<'__w> { + Self::Item { + #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* + } + } } #read_only_data_impl diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index 378e26df101a7..c7ddb9cc83521 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -4,10 +4,7 @@ use proc_macro2::{Ident, Span}; use quote::{format_ident, quote}; use syn::{parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Index}; -use crate::{ - bevy_ecs_path, - world_query::{item_struct, world_query_impl}, -}; +use crate::{bevy_ecs_path, world_query::world_query_impl}; mod field_attr_keywords { syn::custom_keyword!(ignore); @@ -33,8 +30,6 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { let struct_name = ast.ident; - let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site()); - let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site()); let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone()); @@ -81,35 +76,14 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { field_types.push(quote!(#field_ty)); } - let derive_macro_call = quote!(); - - let item_struct = item_struct( - &path, - fields, - &derive_macro_call, - &struct_name, - &visibility, - &item_struct_name, - &field_types, - &user_impl_generics_with_world, - &field_attrs, - &field_visibilities, - &field_idents, - &user_ty_generics, - &user_ty_generics_with_world, - user_where_clauses_with_world, - ); - let world_query_impl = world_query_impl( &path, &struct_name, &visibility, - &item_struct_name, &fetch_struct_name, &field_types, &user_impl_generics, &user_impl_generics_with_world, - &field_idents, &user_ty_generics, &user_ty_generics_with_world, &named_field_idents, @@ -142,8 +116,6 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { }; TokenStream::from(quote! { - #item_struct - const _: () = { #[doc(hidden)] #[doc = concat!( diff --git a/crates/bevy_ecs/macros/src/world_query.rs b/crates/bevy_ecs/macros/src/world_query.rs index f008247657328..77ee532a505f7 100644 --- a/crates/bevy_ecs/macros/src/world_query.rs +++ b/crates/bevy_ecs/macros/src/world_query.rs @@ -34,14 +34,14 @@ pub(crate) fn item_struct( #derive_macro_call #item_attrs #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* + #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w>,)* } }, Fields::Unnamed(_) => quote! { #derive_macro_call #item_attrs #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world( - #( #field_visibilities <#field_types as #path::query::WorldQuery>::Item<'__w>, )* + #( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w>, )* ); }, Fields::Unit => quote! { @@ -55,12 +55,10 @@ pub(crate) fn world_query_impl( path: &syn::Path, struct_name: &Ident, visibility: &Visibility, - item_struct_name: &Ident, fetch_struct_name: &Ident, field_types: &Vec, user_impl_generics: &ImplGenerics, user_impl_generics_with_world: &ImplGenerics, - field_idents: &Vec, user_ty_generics: &TypeGenerics, user_ty_generics_with_world: &TypeGenerics, named_field_idents: &Vec, @@ -98,20 +96,9 @@ pub(crate) fn world_query_impl( unsafe impl #user_impl_generics #path::query::WorldQuery for #struct_name #user_ty_generics #user_where_clauses { - type Item<'__w> = #item_struct_name #user_ty_generics_with_world; type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world; type State = #state_struct_name #user_ty_generics; - fn shrink<'__wlong: '__wshort, '__wshort>( - item: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wlong> - ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wshort> { - #item_struct_name { - #( - #field_idents: <#field_types>::shrink(item.#field_idents), - )* - } - } - fn shrink_fetch<'__wlong: '__wshort, '__wshort>( fetch: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wlong> ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Fetch<'__wshort> { @@ -165,18 +152,6 @@ pub(crate) fn world_query_impl( #(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)* } - /// SAFETY: we call `fetch` for each member that implements `Fetch`. - #[inline(always)] - unsafe fn fetch<'__w>( - _fetch: &mut ::Fetch<'__w>, - _entity: #path::entity::Entity, - _table_row: #path::storage::TableRow, - ) -> ::Item<'__w> { - Self::Item { - #(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)* - } - } - fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { #( <#field_types>::update_component_access(&state.#named_field_idents, _access); )* } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 7919a3232d1b3..fcfd514f68838 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -9,6 +9,7 @@ use crate::{ Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, + change_detection::MaybeLocation, component::{ Component, ComponentId, Components, RequiredComponentConstructor, RequiredComponents, StorageType, Tick, @@ -18,14 +19,12 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT, ON_REPLACE}, + world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE}, }; use alloc::{boxed::Box, vec, vec::Vec}; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::TypeIdMap; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{any::TypeId, ptr::NonNull}; use variadics_please::all_tuples; @@ -151,15 +150,33 @@ use variadics_please::all_tuples; pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s #[doc(hidden)] - fn component_ids( - components: &mut Components, - storages: &mut Storages, - ids: &mut impl FnMut(ComponentId), - ); + fn component_ids(components: &mut Components, ids: &mut impl FnMut(ComponentId)); /// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); + /// Registers components that are required by the components in this [`Bundle`]. + fn register_required_components( + _components: &mut Components, + _required_components: &mut RequiredComponents, + ); +} + +/// Creates a [`Bundle`] by taking it from internal storage. +/// +/// # Safety +/// +/// Manual implementations of this trait are unsupported. +/// That is, there is no safe way to implement this trait, and you must not do so. +/// If you want a type to implement [`Bundle`], you must use [`derive@Bundle`](derive@Bundle). +/// +/// [`Query`]: crate::system::Query +// Some safety points: +// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the +// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called. +// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by +// [`Bundle::component_ids`]. +pub unsafe trait BundleFromComponents { /// Calls `func`, which should return data for each component in the bundle, in the order of /// this bundle's [`Component`]s /// @@ -172,17 +189,12 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { // Ensure that the `OwningPtr` is used correctly F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>, Self: Sized; - - /// Registers components that are required by the components in this [`Bundle`]. - fn register_required_components( - _components: &mut Components, - _storages: &mut Storages, - _required_components: &mut RequiredComponents, - ); } /// The parts from [`Bundle`] that don't require statically knowing the components of the bundle. pub trait DynamicBundle { + /// An operation on the entity that happens _after_ inserting this bundle. + type Effect: BundleEffect; // SAFETY: // The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the // component being fetched. @@ -190,43 +202,38 @@ pub trait DynamicBundle { /// Calls `func` on each value, in the order of this bundle's [`Component`]s. This passes /// ownership of the component values to `func`. #[doc(hidden)] - fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)); + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect; +} + +/// An operation on an [`Entity`] that occurs _after_ inserting the [`Bundle`] that defined this bundle effect. +/// The order of operations is: +/// +/// 1. The [`Bundle`] is inserted on the entity +/// 2. Relevant Hooks are run for the insert, then Observers +/// 3. The [`BundleEffect`] is run. +/// +/// See [`DynamicBundle::Effect`]. +pub trait BundleEffect { + /// Applies this effect to the given `entity`. + fn apply(self, entity: &mut EntityWorldMut); } // SAFETY: // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) // - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. -// - `Bundle::from_components` calls `func` exactly once for C, which is the exact value returned by `Bundle::component_ids`. unsafe impl Bundle for C { - fn component_ids( - components: &mut Components, - storages: &mut Storages, - ids: &mut impl FnMut(ComponentId), - ) { - ids(components.register_component::(storages)); - } - - unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self - where - // Ensure that the `OwningPtr` is used correctly - F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>, - Self: Sized, - { - let ptr = func(ctx); - // Safety: The id given in `component_ids` is for `Self` - unsafe { ptr.read() } + fn component_ids(components: &mut Components, ids: &mut impl FnMut(ComponentId)) { + ids(components.register_component::()); } fn register_required_components( components: &mut Components, - storages: &mut Storages, required_components: &mut RequiredComponents, ) { - let component_id = components.register_component::(storages); + let component_id = components.register_component::(); ::register_required_components( component_id, components, - storages, required_components, 0, &mut Vec::new(), @@ -238,9 +245,25 @@ unsafe impl Bundle for C { } } +// SAFETY: +// - `Bundle::from_components` calls `func` exactly once for C, which is the exact value returned by `Bundle::component_ids`. +unsafe impl BundleFromComponents for C { + unsafe fn from_components(ctx: &mut T, func: &mut F) -> Self + where + // Ensure that the `OwningPtr` is used correctly + F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>, + Self: Sized, + { + let ptr = func(ctx); + // Safety: The id given in `component_ids` is for `Self` + unsafe { ptr.read() } + } +} + impl DynamicBundle for C { + type Effect = (); #[inline] - fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { OwningPtr::make(self, |ptr| func(C::STORAGE_TYPE, ptr)); } } @@ -264,14 +287,39 @@ macro_rules! tuple_impl { // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { - fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){ - $(<$name as Bundle>::component_ids(components, storages, ids);)* + fn component_ids(components: &mut Components, ids: &mut impl FnMut(ComponentId)){ + $(<$name as Bundle>::component_ids(components, ids);)* } fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ $(<$name as Bundle>::get_component_ids(components, ids);)* } + fn register_required_components( + components: &mut Components, + required_components: &mut RequiredComponents, + ) { + $(<$name as Bundle>::register_required_components(components, required_components);)* + } + } + + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + #[allow( + unused_mut, + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + $(#[$meta])* + // SAFETY: + // - `Bundle::component_ids` calls `ids` for each component type in the + // bundle, in the exact order that `DynamicBundle::get_components` is called. + // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. + // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct + // `StorageType` into the callback. + unsafe impl<$($name: BundleFromComponents),*> BundleFromComponents for ($($name,)*) { #[allow( clippy::unused_unit, reason = "Zero-length tuples will generate a function body equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." @@ -286,15 +334,7 @@ macro_rules! tuple_impl { )] // SAFETY: Rust guarantees that tuple calls are evaluated 'left to right'. // https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands - unsafe { ($(<$name as Bundle>::from_components(ctx, func),)*) } - } - - fn register_required_components( - components: &mut Components, - storages: &mut Storages, - required_components: &mut RequiredComponents, - ) { - $(<$name as Bundle>::register_required_components(components, storages, required_components);)* + unsafe { ($(<$name as BundleFromComponents>::from_components(ctx, func),)*) } } } @@ -309,16 +349,21 @@ macro_rules! tuple_impl { )] $(#[$meta])* impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) { + type Effect = ($($name::Effect,)*); + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate a function body equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] #[inline(always)] - fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { #[allow( non_snake_case, reason = "The names of these variables are provided by the caller, not by us." )] let ($(mut $name,)*) = self; - $( - $name.get_components(&mut *func); - )* + ($( + $name.get_components(&mut *func), + )*) } } } @@ -332,6 +377,37 @@ all_tuples!( B ); +/// A trait implemented for [`BundleEffect`] implementations that do nothing. This is used as a type constraint for +/// [`Bundle`] APIs that do not / cannot run [`DynamicBundle::Effect`], such as "batch spawn" APIs. +pub trait NoBundleEffect {} + +macro_rules! after_effect_impl { + ($($after_effect: ident),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + impl<$($after_effect: BundleEffect),*> BundleEffect for ($($after_effect,)*) { + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate a function body equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case.") + ] + fn apply(self, _entity: &mut EntityWorldMut) { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($after_effect,)*) = self; + $($after_effect.apply(_entity);)* + } + } + + impl<$($after_effect: NoBundleEffect),*> NoBundleEffect for ($($after_effect,)*) { } + } +} + +all_tuples!(after_effect_impl, 0, 15, P); + /// For a specific [`World`], this stores a unique value identifying a type of a registered [`Bundle`]. /// /// [`World`]: crate::world::World @@ -392,19 +468,19 @@ impl BundleInfo { /// /// # Safety /// - /// Every ID in `component_ids` must be valid within the World that owns the `BundleInfo`, - /// must have its storage initialized (i.e. columns created in tables, sparse set created), + /// Every ID in `component_ids` must be valid within the World that owns the `BundleInfo` /// and must be in the same order as the source bundle type writes its components in. unsafe fn new( bundle_type_name: &'static str, + storages: &mut Storages, components: &Components, mut component_ids: Vec, id: BundleId, ) -> BundleInfo { + // check for duplicates let mut deduped = component_ids.clone(); deduped.sort_unstable(); deduped.dedup(); - if deduped.len() != component_ids.len() { // TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized let mut seen = >::default(); @@ -427,18 +503,25 @@ impl BundleInfo { panic!("Bundle {bundle_type_name} has duplicate components: {names}"); } + // handle explicit components let explicit_components_len = component_ids.len(); let mut required_components = RequiredComponents::default(); for component_id in component_ids.iter().copied() { // SAFETY: caller has verified that all ids are valid let info = unsafe { components.get_info_unchecked(component_id) }; required_components.merge(info.required_components()); + storages.prepare_component(info); } required_components.remove_explicit_components(&component_ids); + + // handle required components let required_components = required_components .0 .into_iter() .map(|(component_id, v)| { + // Safety: These ids came out of the passed `components`, so they must be valid. + let info = unsafe { components.get_info_unchecked(component_id) }; + storages.prepare_component(info); // This adds required components to the component_ids list _after_ using that list to remove explicitly provided // components. This ordering is important! component_ids.push(component_id); @@ -539,35 +622,27 @@ impl BundleInfo { change_tick: Tick, bundle: T, insert_mode: InsertMode, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) { + caller: MaybeLocation, + ) -> T::Effect { // NOTE: get_components calls this closure on each component in "bundle order". // bundle_info.component_ids are also in "bundle order" let mut bundle_component = 0; - bundle.get_components(&mut |storage_type, component_ptr| { + let after_effect = bundle.get_components(&mut |storage_type, component_ptr| { let component_id = *self.component_ids.get_unchecked(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 requires that + // 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(); match (status, insert_mode) { - (ComponentStatus::Added, _) => column.initialize( - table_row, - component_ptr, - change_tick, - #[cfg(feature = "track_location")] - caller, - ), - (ComponentStatus::Existing, InsertMode::Replace) => column.replace( - table_row, - component_ptr, - change_tick, - #[cfg(feature = "track_location")] - caller, - ), + (ComponentStatus::Added, _) => { + column.initialize(table_row, component_ptr, change_tick, caller); + } + (ComponentStatus::Existing, InsertMode::Replace) => { + column.replace(table_row, component_ptr, change_tick, caller); + } (ComponentStatus::Existing, InsertMode::Keep) => { if let Some(drop_fn) = table.get_drop_for(component_id) { drop_fn(component_ptr); @@ -577,16 +652,10 @@ impl BundleInfo { } StorageType::SparseSet => { let sparse_set = - // SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that + // 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, - #[cfg(feature = "track_location")] - caller, - ); + sparse_set.insert(entity, component_ptr, change_tick, caller); } } bundle_component += 1; @@ -599,10 +668,11 @@ impl BundleInfo { change_tick, table_row, entity, - #[cfg(feature = "track_location")] caller, ); } + + after_effect } /// Internal method to initialize a required component from an [`OwningPtr`]. This should ultimately be called @@ -626,7 +696,7 @@ impl BundleInfo { component_id: ComponentId, storage_type: StorageType, component_ptr: OwningPtr, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { { match storage_type { @@ -635,26 +705,14 @@ impl BundleInfo { // SAFETY: If component_id is in required_components, BundleInfo::new requires that // the target table contains the component. unsafe { table.get_column_mut(component_id).debug_checked_unwrap() }; - column.initialize( - table_row, - component_ptr, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + column.initialize(table_row, component_ptr, change_tick, caller); } StorageType::SparseSet => { let sparse_set = // SAFETY: If component_id is in required_components, BundleInfo::new requires that // a sparse set exists for the component. unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() }; - sparse_set.insert( - entity, - component_ptr, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + sparse_set.insert(entity, component_ptr, change_tick, caller); } } } @@ -1041,8 +1099,8 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, insert_mode: InsertMode, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> EntityLocation { + caller: MaybeLocation, + ) -> (EntityLocation, T::Effect) { let bundle_info = self.bundle_info.as_ref(); let archetype_after_insert = self.archetype_after_insert.as_ref(); let archetype = self.archetype.as_ref(); @@ -1059,7 +1117,6 @@ impl<'w> BundleInserter<'w> { ON_REPLACE, entity, archetype_after_insert.iter_existing(), - #[cfg(feature = "track_location")] caller, ); } @@ -1067,7 +1124,6 @@ impl<'w> BundleInserter<'w> { archetype, entity, archetype_after_insert.iter_existing(), - #[cfg(feature = "track_location")] caller, ); } @@ -1079,7 +1135,7 @@ impl<'w> BundleInserter<'w> { // so this reference can only be promoted from shared to &mut down here, after they have been ran let archetype = self.archetype.as_mut(); - let (new_archetype, new_location) = match &mut self.archetype_move_type { + let (new_archetype, new_location, after_effect) = match &mut self.archetype_move_type { ArchetypeMoveType::SameArchetype => { // SAFETY: Mutable references do not alias and will be dropped after this block let sparse_sets = { @@ -1087,7 +1143,7 @@ impl<'w> BundleInserter<'w> { &mut world.storages.sparse_sets }; - bundle_info.write_components( + let after_effect = bundle_info.write_components( table, sparse_sets, archetype_after_insert, @@ -1097,11 +1153,10 @@ impl<'w> BundleInserter<'w> { self.change_tick, bundle, insert_mode, - #[cfg(feature = "track_location")] caller, ); - (archetype, location) + (archetype, location, after_effect) } ArchetypeMoveType::NewArchetypeSameTable { new_archetype } => { let new_archetype = new_archetype.as_mut(); @@ -1129,7 +1184,7 @@ impl<'w> BundleInserter<'w> { } let new_location = new_archetype.allocate(entity, result.table_row); entities.set(entity.index(), new_location); - bundle_info.write_components( + let after_effect = bundle_info.write_components( table, sparse_sets, archetype_after_insert, @@ -1139,11 +1194,10 @@ impl<'w> BundleInserter<'w> { self.change_tick, bundle, insert_mode, - #[cfg(feature = "track_location")] caller, ); - (new_archetype, new_location) + (new_archetype, new_location, after_effect) } ArchetypeMoveType::NewArchetypeNewTable { new_archetype, @@ -1212,7 +1266,7 @@ impl<'w> BundleInserter<'w> { } } - bundle_info.write_components( + let after_effect = bundle_info.write_components( new_table, sparse_sets, archetype_after_insert, @@ -1222,11 +1276,10 @@ impl<'w> BundleInserter<'w> { self.change_tick, bundle, insert_mode, - #[cfg(feature = "track_location")] caller, ); - (new_archetype, new_location) + (new_archetype, new_location, after_effect) } }; @@ -1241,7 +1294,6 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_added(), - #[cfg(feature = "track_location")] caller, ); if new_archetype.has_add_observer() { @@ -1249,7 +1301,6 @@ impl<'w> BundleInserter<'w> { ON_ADD, entity, archetype_after_insert.iter_added(), - #[cfg(feature = "track_location")] caller, ); } @@ -1260,7 +1311,6 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_inserted(), - #[cfg(feature = "track_location")] caller, ); if new_archetype.has_insert_observer() { @@ -1268,7 +1318,6 @@ impl<'w> BundleInserter<'w> { ON_INSERT, entity, archetype_after_insert.iter_inserted(), - #[cfg(feature = "track_location")] caller, ); } @@ -1280,7 +1329,6 @@ impl<'w> BundleInserter<'w> { new_archetype, entity, archetype_after_insert.iter_added(), - #[cfg(feature = "track_location")] caller, ); if new_archetype.has_insert_observer() { @@ -1288,7 +1336,6 @@ impl<'w> BundleInserter<'w> { ON_INSERT, entity, archetype_after_insert.iter_added(), - #[cfg(feature = "track_location")] caller, ); } @@ -1296,7 +1343,7 @@ impl<'w> BundleInserter<'w> { } } - new_location + (new_location, after_effect) } #[inline] @@ -1370,11 +1417,11 @@ impl<'w> BundleSpawner<'w> { &mut self, entity: Entity, bundle: T, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> EntityLocation { + caller: MaybeLocation, + ) -> (EntityLocation, T::Effect) { // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid let bundle_info = self.bundle_info.as_ref(); - let location = { + let (location, after_effect) = { let table = self.table.as_mut(); let archetype = self.archetype.as_mut(); @@ -1385,7 +1432,7 @@ impl<'w> BundleSpawner<'w> { }; let table_row = table.allocate(entity); let location = archetype.allocate(entity, table_row); - bundle_info.write_components( + let after_effect = bundle_info.write_components( table, sparse_sets, &SpawnBundleStatus, @@ -1395,11 +1442,10 @@ impl<'w> BundleSpawner<'w> { self.change_tick, bundle, InsertMode::Replace, - #[cfg(feature = "track_location")] caller, ); entities.set(entity.index(), location); - location + (location, after_effect) }; // SAFETY: We have no outstanding mutable references to world as they were dropped @@ -1413,7 +1459,6 @@ impl<'w> BundleSpawner<'w> { archetype, entity, bundle_info.iter_contributed_components(), - #[cfg(feature = "track_location")] caller, ); if archetype.has_add_observer() { @@ -1421,7 +1466,6 @@ impl<'w> BundleSpawner<'w> { ON_ADD, entity, bundle_info.iter_contributed_components(), - #[cfg(feature = "track_location")] caller, ); } @@ -1429,7 +1473,6 @@ impl<'w> BundleSpawner<'w> { archetype, entity, bundle_info.iter_contributed_components(), - #[cfg(feature = "track_location")] caller, ); if archetype.has_insert_observer() { @@ -1437,13 +1480,12 @@ impl<'w> BundleSpawner<'w> { ON_INSERT, entity, bundle_info.iter_contributed_components(), - #[cfg(feature = "track_location")] caller, ); } }; - location + (location, after_effect) } /// # Safety @@ -1452,19 +1494,12 @@ impl<'w> BundleSpawner<'w> { pub unsafe fn spawn( &mut self, bundle: T, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> Entity { + caller: MaybeLocation, + ) -> (Entity, T::Effect) { let entity = self.entities().alloc(); // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type - unsafe { - self.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_location")] - caller, - ); - } - entity + let (_, after_effect) = unsafe { self.spawn_non_existent(entity, bundle, caller) }; + (entity, after_effect) } #[inline] @@ -1540,14 +1575,14 @@ impl Bundles { let bundle_infos = &mut self.bundle_infos; let id = *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { let mut component_ids= Vec::new(); - T::component_ids(components, storages, &mut |id| component_ids.push(id)); + T::component_ids(components, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); let bundle_info = // SAFETY: T::component_id ensures: // - its info was created // - appropriate storage for it has been initialized. // - it was created in the same order as the components in T - unsafe { BundleInfo::new(core::any::type_name::(), components, component_ids, id) }; + unsafe { BundleInfo::new(core::any::type_name::(), storages, components, component_ids, id) }; bundle_infos.push(bundle_info); id }); @@ -1577,7 +1612,7 @@ impl Bundles { }; // SAFETY: this is sound because the contributed_components Vec for explicit_bundle_id will not be accessed mutably as // part of init_dynamic_info. No mutable references will be created and the allocation will remain valid. - self.init_dynamic_info(components, core::slice::from_raw_parts(ptr, len)) + self.init_dynamic_info(storages, components, core::slice::from_raw_parts(ptr, len)) }; self.contributed_bundle_ids.insert(TypeId::of::(), id); id @@ -1615,6 +1650,7 @@ impl Bundles { /// provided [`Components`]. pub(crate) fn init_dynamic_info( &mut self, + storages: &mut Storages, components: &Components, component_ids: &[ComponentId], ) -> BundleId { @@ -1626,8 +1662,12 @@ impl Bundles { .raw_entry_mut() .from_key(component_ids) .or_insert_with(|| { - let (id, storages) = - initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)); + let (id, storages) = initialize_dynamic_bundle( + bundle_infos, + storages, + components, + Vec::from(component_ids), + ); // SAFETY: The ID always increases when new bundles are added, and so, the ID is unique. unsafe { self.dynamic_bundle_storages @@ -1645,6 +1685,7 @@ impl Bundles { /// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`]. pub(crate) fn init_component_info( &mut self, + storages: &mut Storages, components: &Components, component_id: ComponentId, ) -> BundleId { @@ -1653,8 +1694,12 @@ impl Bundles { .dynamic_component_bundle_ids .entry(component_id) .or_insert_with(|| { - let (id, storage_type) = - initialize_dynamic_bundle(bundle_infos, components, vec![component_id]); + let (id, storage_type) = initialize_dynamic_bundle( + bundle_infos, + storages, + components, + vec![component_id], + ); self.dynamic_component_storages.insert(id, storage_type[0]); id }); @@ -1666,6 +1711,7 @@ impl Bundles { /// and initializes a [`BundleInfo`]. fn initialize_dynamic_bundle( bundle_infos: &mut Vec, + storages: &mut Storages, components: &Components, component_ids: Vec, ) -> (BundleId, Vec) { @@ -1681,7 +1727,7 @@ fn initialize_dynamic_bundle( let id = BundleId(bundle_infos.len()); let bundle_info = // SAFETY: `component_ids` are valid as they were just checked - unsafe { BundleInfo::new("", components, component_ids, id) }; + unsafe { BundleInfo::new("", storages, components, component_ids, id) }; bundle_infos.push(bundle_info); (id, storage_types) @@ -1704,7 +1750,6 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{component::HookContext, prelude::*, world::DeferredWorld}; use alloc::vec; diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 0884fbd59ebb3..542736e1115ee 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -7,14 +7,13 @@ use crate::{ }; use alloc::borrow::ToOwned; use bevy_ptr::{Ptr, UnsafeCellDeref}; +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; use core::{ + marker::PhantomData, mem, ops::{Deref, DerefMut}, -}; -#[cfg(feature = "track_location")] -use { - bevy_ptr::ThinSlicePtr, - core::{cell::UnsafeCell, panic::Location}, + panic::Location, }; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. @@ -73,8 +72,7 @@ pub trait DetectChanges { fn last_changed(&self) -> Tick; /// The location that last caused this to change. - #[cfg(feature = "track_location")] - fn changed_by(&self) -> &'static Location<'static>; + fn changed_by(&self) -> MaybeLocation; } /// Types that implement reliable change detection. @@ -225,7 +223,7 @@ pub trait DetectChangesMut: DetectChanges { /// let new_score = 0; /// if let Some(Score(previous_score)) = score.replace_if_neq(Score(new_score)) { /// // If `score` change, emit a `ScoreChanged` event. - /// score_changed.send(ScoreChanged { + /// score_changed.write(ScoreChanged { /// current: new_score, /// previous: previous_score, /// }); @@ -343,9 +341,8 @@ macro_rules! change_detection_impl { } #[inline] - #[cfg(feature = "track_location")] - fn changed_by(&self) -> &'static Location<'static> { - self.changed_by + fn changed_by(&self) -> MaybeLocation { + self.changed_by.copied() } } @@ -376,20 +373,14 @@ macro_rules! change_detection_mut_impl { #[track_caller] fn set_changed(&mut self) { *self.ticks.changed = self.ticks.this_run; - #[cfg(feature = "track_location")] - { - *self.changed_by = Location::caller(); - } + self.changed_by.assign(MaybeLocation::caller()); } #[inline] #[track_caller] fn set_last_changed(&mut self, last_changed: Tick) { *self.ticks.changed = last_changed; - #[cfg(feature = "track_location")] - { - *self.changed_by = Location::caller(); - } + self.changed_by.assign(MaybeLocation::caller()); } #[inline] @@ -403,10 +394,7 @@ macro_rules! change_detection_mut_impl { #[track_caller] fn deref_mut(&mut self) -> &mut Self::Target { self.set_changed(); - #[cfg(feature = "track_location")] - { - *self.changed_by = Location::caller(); - } + self.changed_by.assign(MaybeLocation::caller()); self.value } } @@ -444,8 +432,7 @@ macro_rules! impl_methods { last_run: self.ticks.last_run, this_run: self.ticks.this_run, }, - #[cfg(feature = "track_location")] - changed_by: self.changed_by, + changed_by: self.changed_by.as_deref_mut(), } } @@ -475,7 +462,6 @@ macro_rules! impl_methods { Mut { value: f(self.value), ticks: self.ticks, - #[cfg(feature = "track_location")] changed_by: self.changed_by, } } @@ -489,7 +475,6 @@ macro_rules! impl_methods { value.map(|value| Mut { value, ticks: self.ticks, - #[cfg(feature = "track_location")] changed_by: self.changed_by, }) } @@ -503,7 +488,6 @@ macro_rules! impl_methods { value.map(|value| Mut { value, ticks: self.ticks, - #[cfg(feature = "track_location")] changed_by: self.changed_by, }) } @@ -614,8 +598,7 @@ impl<'w> From> for Ticks<'w> { pub struct Res<'w, T: ?Sized + Resource> { pub(crate) value: &'w T, pub(crate) ticks: Ticks<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w &'static Location<'static>>, } impl<'w, T: Resource> Res<'w, T> { @@ -631,7 +614,6 @@ impl<'w, T: Resource> Res<'w, T> { Self { value: this.value, ticks: this.ticks.clone(), - #[cfg(feature = "track_location")] changed_by: this.changed_by, } } @@ -649,8 +631,7 @@ impl<'w, T: Resource> From> for Res<'w, T> { Self { value: res.value, ticks: res.ticks.into(), - #[cfg(feature = "track_location")] - changed_by: res.changed_by, + changed_by: res.changed_by.map(|changed_by| &*changed_by), } } } @@ -662,7 +643,6 @@ impl<'w, T: Resource> From> for Ref<'w, T> { Self { value: res.value, ticks: res.ticks, - #[cfg(feature = "track_location")] changed_by: res.changed_by, } } @@ -695,8 +675,7 @@ impl_debug!(Res<'w, T>, Resource); pub struct ResMut<'w, T: ?Sized + Resource> { pub(crate) value: &'w mut T, pub(crate) ticks: TicksMut<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'w mut &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w mut &'static Location<'static>>, } impl<'w, 'a, T: Resource> IntoIterator for &'a ResMut<'w, T> @@ -736,7 +715,6 @@ impl<'w, T: Resource> From> for Mut<'w, T> { Mut { value: other.value, ticks: other.ticks, - #[cfg(feature = "track_location")] changed_by: other.changed_by, } } @@ -756,8 +734,7 @@ impl<'w, T: Resource> From> for Mut<'w, T> { pub struct NonSendMut<'w, T: ?Sized + 'static> { pub(crate) value: &'w mut T, pub(crate) ticks: TicksMut<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'w mut &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w mut &'static Location<'static>>, } change_detection_impl!(NonSendMut<'w, T>, T,); @@ -772,7 +749,6 @@ impl<'w, T: 'static> From> for Mut<'w, T> { Mut { value: other.value, ticks: other.ticks, - #[cfg(feature = "track_location")] changed_by: other.changed_by, } } @@ -805,8 +781,7 @@ impl<'w, T: 'static> From> for Mut<'w, T> { pub struct Ref<'w, T: ?Sized> { pub(crate) value: &'w T, pub(crate) ticks: Ticks<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w &'static Location<'static>>, } impl<'w, T: ?Sized> Ref<'w, T> { @@ -823,7 +798,6 @@ impl<'w, T: ?Sized> Ref<'w, T> { Ref { value: f(self.value), ticks: self.ticks, - #[cfg(feature = "track_location")] changed_by: self.changed_by, } } @@ -845,7 +819,7 @@ impl<'w, T: ?Sized> Ref<'w, T> { changed: &'w Tick, last_run: Tick, this_run: Tick, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation<&'w &'static Location<'static>>, ) -> Ref<'w, T> { Ref { value, @@ -855,10 +829,18 @@ impl<'w, T: ?Sized> Ref<'w, T> { last_run, this_run, }, - #[cfg(feature = "track_location")] changed_by: caller, } } + + /// Overwrite the `last_run` and `this_run` tick that are used for change detection. + /// + /// This is an advanced feature. `Ref`s are usually _created_ by engine-internal code and + /// _consumed_ by end-user code. + pub fn set_ticks(&mut self, last_run: Tick, this_run: Tick) { + self.ticks.last_run = last_run; + self.ticks.this_run = this_run; + } } impl<'w, 'a, T> IntoIterator for &'a Ref<'w, T> @@ -938,8 +920,7 @@ impl_debug!(Ref<'w, T>,); pub struct Mut<'w, T: ?Sized> { pub(crate) value: &'w mut T, pub(crate) ticks: TicksMut<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'w mut &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w mut &'static Location<'static>>, } impl<'w, T: ?Sized> Mut<'w, T> { @@ -964,7 +945,7 @@ impl<'w, T: ?Sized> Mut<'w, T> { last_changed: &'w mut Tick, last_run: Tick, this_run: Tick, - #[cfg(feature = "track_location")] caller: &'w mut &'static Location<'static>, + caller: MaybeLocation<&'w mut &'static Location<'static>>, ) -> Self { Self { value, @@ -974,10 +955,18 @@ impl<'w, T: ?Sized> Mut<'w, T> { last_run, this_run, }, - #[cfg(feature = "track_location")] changed_by: caller, } } + + /// Overwrite the `last_run` and `this_run` tick that are used for change detection. + /// + /// This is an advanced feature. `Mut`s are usually _created_ by engine-internal code and + /// _consumed_ by end-user code. + pub fn set_ticks(&mut self, last_run: Tick, this_run: Tick) { + self.ticks.last_run = last_run; + self.ticks.this_run = this_run; + } } impl<'w, T: ?Sized> From> for Ref<'w, T> { @@ -985,8 +974,7 @@ impl<'w, T: ?Sized> From> for Ref<'w, T> { Self { value: mut_ref.value, ticks: mut_ref.ticks.into(), - #[cfg(feature = "track_location")] - changed_by: mut_ref.changed_by, + changed_by: mut_ref.changed_by.map(|changed_by| &*changed_by), } } } @@ -1032,8 +1020,7 @@ impl_debug!(Mut<'w, T>,); pub struct MutUntyped<'w> { pub(crate) value: PtrMut<'w>, pub(crate) ticks: TicksMut<'w>, - #[cfg(feature = "track_location")] - pub(crate) changed_by: &'w mut &'static Location<'static>, + pub(crate) changed_by: MaybeLocation<&'w mut &'static Location<'static>>, } impl<'w> MutUntyped<'w> { @@ -1058,8 +1045,7 @@ impl<'w> MutUntyped<'w> { last_run: self.ticks.last_run, this_run: self.ticks.this_run, }, - #[cfg(feature = "track_location")] - changed_by: self.changed_by, + changed_by: self.changed_by.as_deref_mut(), } } @@ -1110,7 +1096,6 @@ impl<'w> MutUntyped<'w> { Mut { value: f(self.value), ticks: self.ticks, - #[cfg(feature = "track_location")] changed_by: self.changed_by, } } @@ -1125,7 +1110,6 @@ impl<'w> MutUntyped<'w> { value: unsafe { self.value.deref_mut() }, ticks: self.ticks, // SAFETY: `caller` is `Aligned`. - #[cfg(feature = "track_location")] changed_by: self.changed_by, } } @@ -1152,9 +1136,8 @@ impl<'w> DetectChanges for MutUntyped<'w> { } #[inline] - #[cfg(feature = "track_location")] - fn changed_by(&self) -> &'static Location<'static> { - self.changed_by + fn changed_by(&self) -> MaybeLocation { + self.changed_by.copied() } } @@ -1165,20 +1148,14 @@ impl<'w> DetectChangesMut for MutUntyped<'w> { #[track_caller] fn set_changed(&mut self) { *self.ticks.changed = self.ticks.this_run; - #[cfg(feature = "track_location")] - { - *self.changed_by = Location::caller(); - } + self.changed_by.assign(MaybeLocation::caller()); } #[inline] #[track_caller] fn set_last_changed(&mut self, last_changed: Tick) { *self.ticks.changed = last_changed; - #[cfg(feature = "track_location")] - { - *self.changed_by = Location::caller(); - } + self.changed_by.assign(MaybeLocation::caller()); } #[inline] @@ -1201,62 +1178,294 @@ impl<'w, T> From> for MutUntyped<'w> { MutUntyped { value: value.value.into(), ticks: value.ticks, - #[cfg(feature = "track_location")] changed_by: value.changed_by, } } } -/// A type alias to [`&'static Location<'static>`](std::panic::Location) when the `track_location` feature is -/// enabled, and the unit type `()` when it is not. +/// A value that contains a `T` if the `track_location` feature is enabled, +/// and is a ZST if it is not. /// -/// This is primarily used in places where `#[cfg(...)]` attributes are not allowed, such as -/// function return types. Because unit is a zero-sized type, it is the equivalent of not using a -/// `Location` at all. +/// The overall API is similar to [`Option`], but whether the value is `Some` or `None` is set at compile +/// time and is the same for all values. /// -/// Please use this type sparingly: prefer normal `#[cfg(...)]` attributes when possible. -#[cfg(feature = "track_location")] -pub(crate) type MaybeLocation = &'static Location<'static>; - -/// A type alias to [`&'static Location<'static>`](std::panic::Location) when the `track_location` feature is -/// enabled, and the unit type `()` when it is not. +/// If the `track_location` feature is disabled, then all functions on this type that return +/// an `MaybeLocation` will have an empty body and should be removed by the optimizer. /// -/// This is primarily used in places where `#[cfg(...)]` attributes are not allowed, such as -/// function return types. Because unit is a zero-sized type, it is the equivalent of not using a -/// `Location` at all. -/// -/// Please use this type sparingly: prefer normal `#[cfg(...)]` attributes when possible. -#[cfg(not(feature = "track_location"))] -pub(crate) type MaybeLocation = (); +/// This allows code to be written that will be checked by the compiler even when the feature is disabled, +/// but that will be entirely removed during compilation. +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MaybeLocation> { + #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] + marker: PhantomData, + #[cfg(feature = "track_location")] + value: T, +} -/// A type alias to `&UnsafeCell<&'static Location<'static>>` when the `track_location` -/// feature is enabled, and the unit type `()` when it is not. -/// -/// See [`MaybeLocation`] for further information. -#[cfg(feature = "track_location")] -pub(crate) type MaybeUnsafeCellLocation<'a> = &'a UnsafeCell<&'static Location<'static>>; +impl core::fmt::Display for MaybeLocation { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(feature = "track_location")] + { + self.value.fmt(_f)?; + } + Ok(()) + } +} -/// A type alias to `&UnsafeCell<&'static Location<'static>>` when the `track_location` -/// feature is enabled, and the unit type `()` when it is not. -/// -/// See [`MaybeLocation`] for further information. -#[cfg(not(feature = "track_location"))] -pub(crate) type MaybeUnsafeCellLocation<'a> = (); +impl MaybeLocation { + /// Constructs a new `MaybeLocation` that wraps the given value. + /// + /// This may only accept `Copy` types, + /// since it needs to drop the value if the `track_location` feature is disabled, + /// and non-`Copy` types cannot be dropped in `const` context. + /// Use [`new_with`][Self::new_with] if you need to construct a non-`Copy` value. + /// + /// # See also + /// - [`new_with`][Self::new_with] to initialize using a closure. + /// - [`new_with_flattened`][Self::new_with_flattened] to initialize using a closure that returns an `Option>`. + #[inline] + pub const fn new(_value: T) -> Self + where + T: Copy, + { + Self { + #[cfg(feature = "track_location")] + value: _value, + marker: PhantomData, + } + } -/// A type alias to `ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>` when the -/// `track_location` feature is enabled, and the unit type `()` when it is not. -/// -/// See [`MaybeLocation`] for further information. -#[cfg(feature = "track_location")] -pub(crate) type MaybeThinSlicePtrLocation<'w> = - ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>; + /// Constructs a new `MaybeLocation` that wraps the result of the given closure. + /// + /// # See also + /// - [`new`][Self::new] to initialize using a value. + /// - [`new_with_flattened`][Self::new_with_flattened] to initialize using a closure that returns an `Option>`. + #[inline] + pub fn new_with(_f: impl FnOnce() -> T) -> Self { + Self { + #[cfg(feature = "track_location")] + value: _f(), + marker: PhantomData, + } + } -/// A type alias to `ThinSlicePtr<'w, UnsafeCell<&'static Location<'static>>>` when the -/// `track_location` feature is enabled, and the unit type `()` when it is not. -/// -/// See [`MaybeLocation`] for further information. -#[cfg(not(feature = "track_location"))] -pub(crate) type MaybeThinSlicePtrLocation<'w> = (); + /// Maps an `MaybeLocation `to `MaybeLocation` by applying a function to a contained value. + #[inline] + pub fn map(self, _f: impl FnOnce(T) -> U) -> MaybeLocation { + MaybeLocation { + #[cfg(feature = "track_location")] + value: _f(self.value), + marker: PhantomData, + } + } + + /// Converts a pair of `MaybeLocation` values to an `MaybeLocation` of a tuple. + #[inline] + pub fn zip(self, _other: MaybeLocation) -> MaybeLocation<(T, U)> { + MaybeLocation { + #[cfg(feature = "track_location")] + value: (self.value, _other.value), + marker: PhantomData, + } + } + + /// Returns the contained value or a default. + /// If the `track_location` feature is enabled, this always returns the contained value. + /// If it is disabled, this always returns `T::Default()`. + #[inline] + pub fn unwrap_or_default(self) -> T + where + T: Default, + { + self.into_option().unwrap_or_default() + } + + /// Converts an `MaybeLocation` to an [`Option`] to allow run-time branching. + /// If the `track_location` feature is enabled, this always returns `Some`. + /// If it is disabled, this always returns `None`. + #[inline] + pub fn into_option(self) -> Option { + #[cfg(feature = "track_location")] + { + Some(self.value) + } + #[cfg(not(feature = "track_location"))] + { + None + } + } +} + +impl MaybeLocation> { + /// Constructs a new `MaybeLocation` that wraps the result of the given closure. + /// If the closure returns `Some`, it unwraps the inner value. + /// + /// # See also + /// - [`new`][Self::new] to initialize using a value. + /// - [`new_with`][Self::new_with] to initialize using a closure. + #[inline] + pub fn new_with_flattened(_f: impl FnOnce() -> Option>) -> Self { + Self { + #[cfg(feature = "track_location")] + value: _f().map(|value| value.value), + marker: PhantomData, + } + } + + /// Transposes a `MaybeLocation` of an [`Option`] into an [`Option`] of a `MaybeLocation`. + /// + /// This can be useful if you want to use the `?` operator to exit early + /// if the `track_location` feature is enabled but the value is not found. + /// + /// If the `track_location` feature is enabled, + /// this returns `Some` if the inner value is `Some` + /// and `None` if the inner value is `None`. + /// + /// If it is disabled, this always returns `Some`. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::{change_detection::MaybeLocation, world::World}; + /// # use core::panic::Location; + /// # + /// # fn test() -> Option<()> { + /// let mut world = World::new(); + /// let entity = world.spawn(()).id(); + /// let location: MaybeLocation>> = + /// world.entities().entity_get_spawned_or_despawned_by(entity); + /// let location: MaybeLocation<&'static Location<'static>> = location.transpose()?; + /// # Some(()) + /// # } + /// # test(); + /// ``` + /// + /// # See also + /// + /// - [`into_option`][Self::into_option] to convert to an `Option>`. + /// When used with [`Option::flatten`], this will have a similar effect, + /// but will return `None` when the `track_location` feature is disabled. + #[inline] + pub fn transpose(self) -> Option> { + #[cfg(feature = "track_location")] + { + self.value.map(|value| MaybeLocation { + value, + marker: PhantomData, + }) + } + #[cfg(not(feature = "track_location"))] + { + Some(MaybeLocation { + marker: PhantomData, + }) + } + } +} + +impl MaybeLocation<&T> { + /// Maps an `MaybeLocation<&T>` to an `MaybeLocation` by copying the contents. + #[inline] + pub const fn copied(&self) -> MaybeLocation + where + T: Copy, + { + MaybeLocation { + #[cfg(feature = "track_location")] + value: *self.value, + marker: PhantomData, + } + } +} + +impl MaybeLocation<&mut T> { + /// Maps an `MaybeLocation<&mut T>` to an `MaybeLocation` by copying the contents. + #[inline] + pub const fn copied(&self) -> MaybeLocation + where + T: Copy, + { + MaybeLocation { + #[cfg(feature = "track_location")] + value: *self.value, + marker: PhantomData, + } + } + + /// Assigns the contents of an `MaybeLocation` to an `MaybeLocation<&mut T>`. + #[inline] + pub fn assign(&mut self, _value: MaybeLocation) { + #[cfg(feature = "track_location")] + { + *self.value = _value.value; + } + } +} + +impl MaybeLocation { + /// Converts from `&MaybeLocation` to `MaybeLocation<&T>`. + #[inline] + pub const fn as_ref(&self) -> MaybeLocation<&T> { + MaybeLocation { + #[cfg(feature = "track_location")] + value: &self.value, + marker: PhantomData, + } + } + + /// Converts from `&mut MaybeLocation` to `MaybeLocation<&mut T>`. + #[inline] + pub const fn as_mut(&mut self) -> MaybeLocation<&mut T> { + MaybeLocation { + #[cfg(feature = "track_location")] + value: &mut self.value, + marker: PhantomData, + } + } + + /// Converts from `&MaybeLocation` to `MaybeLocation<&T::Target>`. + #[inline] + pub fn as_deref(&self) -> MaybeLocation<&T::Target> + where + T: Deref, + { + MaybeLocation { + #[cfg(feature = "track_location")] + value: &*self.value, + marker: PhantomData, + } + } + + /// Converts from `&mut MaybeLocation` to `MaybeLocation<&mut T::Target>`. + #[inline] + pub fn as_deref_mut(&mut self) -> MaybeLocation<&mut T::Target> + where + T: DerefMut, + { + MaybeLocation { + #[cfg(feature = "track_location")] + value: &mut *self.value, + marker: PhantomData, + } + } +} + +impl MaybeLocation { + /// Returns the source location of the caller of this function. If that function's caller is + /// annotated then its call location will be returned, and so on up the stack to the first call + /// within a non-tracked function body. + #[inline] + #[track_caller] + pub fn caller() -> Self { + // Note that this cannot use `new_with`, since `FnOnce` invocations cannot be annotated with `#[track_caller]`. + MaybeLocation { + #[cfg(feature = "track_location")] + value: Location::caller(), + marker: PhantomData, + } + } +} #[cfg(test)] mod tests { @@ -1264,13 +1473,11 @@ mod tests { use bevy_ptr::PtrMut; use bevy_reflect::{FromType, ReflectFromPtr}; use core::ops::{Deref, DerefMut}; - #[cfg(feature = "track_location")] - use core::panic::Location; use crate::{ - self as bevy_ecs, change_detection::{ - Mut, NonSendMut, Ref, ResMut, TicksMut, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, + MaybeLocation, Mut, NonSendMut, Ref, ResMut, TicksMut, CHECK_TICK_THRESHOLD, + MAX_CHANGE_AGE, }, component::{Component, ComponentTicks, Tick}, system::{IntoSystem, Single, System}, @@ -1396,14 +1603,12 @@ mod tests { this_run: Tick::new(4), }; let mut res = R {}; - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let res_mut = ResMut { value: &mut res, ticks, - #[cfg(feature = "track_location")] - changed_by: &mut caller, + changed_by: caller.as_mut(), }; let into_mut: Mut = res_mut.into(); @@ -1420,8 +1625,7 @@ mod tests { changed: Tick::new(3), }; let mut res = R {}; - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let val = Mut::new( &mut res, @@ -1429,8 +1633,7 @@ mod tests { &mut component_ticks.changed, Tick::new(2), // last_run Tick::new(4), // this_run - #[cfg(feature = "track_location")] - &mut caller, + caller.as_mut(), ); assert!(!val.is_added()); @@ -1450,14 +1653,12 @@ mod tests { this_run: Tick::new(4), }; let mut res = R {}; - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let non_send_mut = NonSendMut { value: &mut res, ticks, - #[cfg(feature = "track_location")] - changed_by: &mut caller, + changed_by: caller.as_mut(), }; let into_mut: Mut = non_send_mut.into(); @@ -1486,14 +1687,12 @@ mod tests { }; let mut outer = Outer(0); - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let ptr = Mut { value: &mut outer, ticks, - #[cfg(feature = "track_location")] - changed_by: &mut caller, + changed_by: caller.as_mut(), }; assert!(!ptr.is_changed()); @@ -1576,14 +1775,12 @@ mod tests { }; let mut value: i32 = 5; - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let value = MutUntyped { value: PtrMut::from(&mut value), ticks, - #[cfg(feature = "track_location")] - changed_by: &mut caller, + changed_by: caller.as_mut(), }; let reflect_from_ptr = >::from_type(); @@ -1614,14 +1811,12 @@ mod tests { this_run: Tick::new(4), }; let mut c = C {}; - #[cfg(feature = "track_location")] - let mut caller = Location::caller(); + let mut caller = MaybeLocation::caller(); let mut_typed = Mut { value: &mut c, ticks, - #[cfg(feature = "track_location")] - changed_by: &mut caller, + changed_by: caller.as_mut(), }; let into_mut: MutUntyped = mut_typed.into(); diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 190910d7d0b96..0eaa329c61a30 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,15 +1,14 @@ //! Types for declaring and storing [`Component`]s. use crate::{ - self as bevy_ecs, archetype::ArchetypeFlags, bundle::BundleInfo, - change_detection::MAX_CHANGE_AGE, + change_detection::{MaybeLocation, MAX_CHANGE_AGE}, entity::{ComponentCloneCtx, Entity}, query::DebugCheckedUnwrap, resource::Resource, - storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - system::{Local, SystemParam}, + storage::{SparseSetIndex, SparseSets, Table, TableRow}, + system::{Commands, Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, }; #[cfg(feature = "bevy_reflect")] @@ -29,7 +28,6 @@ use core::{ fmt::Debug, marker::PhantomData, mem::needs_drop, - panic::Location, }; use disqualified::ShortName; use thiserror::Error; @@ -179,10 +177,6 @@ pub use bevy_ecs_macros::require; /// } /// /// # 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()); @@ -401,13 +395,43 @@ pub trait Component: Send + Sync + 'static { type Mutability: ComponentMutability; /// Called when registering this component, allowing mutable access to its [`ComponentHooks`]. - fn register_component_hooks(_hooks: &mut 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 + } + + /// Gets the `on_insert` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_insert() -> Option { + None + } + + /// Gets the `on_replace` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_replace() -> Option { + None + } + + /// Gets the `on_remove` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_remove() -> Option { + None + } + + /// Gets the `on_despawn` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_despawn() -> Option { + None + } /// Registers required components. fn register_required_components( _component_id: ComponentId, _components: &mut Components, - _storages: &mut Storages, _required_components: &mut RequiredComponents, _inheritance_depth: u16, _recursion_check_stack: &mut Vec, @@ -416,10 +440,20 @@ pub trait Component: Send + Sync + 'static { /// Called when registering this component, allowing to override clone function (or disable cloning altogether) for this component. /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::default_handler() + /// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority. + #[inline] + fn clone_behavior() -> ComponentCloneBehavior { + ComponentCloneBehavior::Default } + + /// Visits entities stored on the component. + #[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)) {} } mod private { @@ -509,7 +543,7 @@ pub struct HookContext { /// The [`ComponentId`] this hook was invoked for. pub component_id: ComponentId, /// The caller location is `Some` if the `track_caller` feature is enabled. - pub caller: Option<&'static Location<'static>>, + pub caller: MaybeLocation, } /// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`]. @@ -575,6 +609,26 @@ pub struct ComponentHooks { } impl ComponentHooks { + pub(crate) fn update_from_component(&mut self) -> &mut Self { + if let Some(hook) = C::on_add() { + self.on_add(hook); + } + if let Some(hook) = C::on_insert() { + self.on_insert(hook); + } + if let Some(hook) = C::on_replace() { + self.on_replace(hook); + } + if let Some(hook) = C::on_remove() { + self.on_remove(hook); + } + if let Some(hook) = C::on_despawn() { + self.on_despawn(hook); + } + + self + } + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as /// adding all of its components. @@ -743,6 +797,12 @@ impl ComponentInfo { self.descriptor.mutable } + /// Returns [`ComponentCloneBehavior`] of the current component. + #[inline] + pub fn clone_behavior(&self) -> &ComponentCloneBehavior { + &self.descriptor.clone_behavior + } + /// Returns the [`TypeId`] of the underlying component type. /// Returns `None` if the component does not correspond to a Rust type. #[inline] @@ -899,6 +959,7 @@ pub struct ComponentDescriptor { // None if the underlying type doesn't need to be dropped drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, + clone_behavior: ComponentCloneBehavior, } // We need to ignore the `drop` field in our `Debug` impl @@ -911,6 +972,7 @@ impl Debug for ComponentDescriptor { .field("type_id", &self.type_id) .field("layout", &self.layout) .field("mutable", &self.mutable) + .field("clone_behavior", &self.clone_behavior) .finish() } } @@ -936,6 +998,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: T::Mutability::MUTABLE, + clone_behavior: T::clone_behavior(), } } @@ -950,6 +1013,7 @@ impl ComponentDescriptor { layout: Layout, drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, + clone_behavior: ComponentCloneBehavior, ) -> Self { Self { name: name.into(), @@ -959,6 +1023,7 @@ impl ComponentDescriptor { layout, drop, mutable, + clone_behavior, } } @@ -976,6 +1041,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, + clone_behavior: ComponentCloneBehavior::Default, } } @@ -988,6 +1054,7 @@ impl ComponentDescriptor { layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, + clone_behavior: ComponentCloneBehavior::Default, } } @@ -1018,107 +1085,55 @@ impl ComponentDescriptor { } /// Function type that can be used to clone an entity. -pub type ComponentCloneFn = fn(&mut DeferredWorld, &mut ComponentCloneCtx); - -/// A struct instructing which clone handler to use when cloning a component. -#[derive(Debug)] -pub struct ComponentCloneHandler(Option); +pub type ComponentCloneFn = fn(&mut Commands, &mut ComponentCloneCtx); -impl ComponentCloneHandler { - /// Use the global default function to clone the component with this handler. - pub fn default_handler() -> Self { - Self(None) - } - - /// Do not clone the component. When a command to clone an entity is issued, component with this handler will be skipped. - pub fn ignore() -> Self { - Self(Some(component_clone_ignore)) - } +/// The clone behavior to use when cloning a [`Component`]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum ComponentCloneBehavior { + /// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`]) + #[default] + Default, + /// Do not clone this component. + Ignore, + /// Uses a custom [`ComponentCloneFn`]. + Custom(ComponentCloneFn), + /// Uses a [`ComponentCloneFn`] that produces an empty version of the given relationship target. + // TODO: this exists so that the current scene spawning code can know when to skip these components. + // When we move to actually cloning entities in scene spawning code, this should be removed in favor of Custom, as the + // distinction will no longer be necessary. + RelationshipTarget(ComponentCloneFn), +} +impl ComponentCloneBehavior { /// Set clone handler based on `Clone` trait. /// /// If set as a handler for a component that is not the same as the one used to create this handler, it will panic. - pub fn clone_handler() -> Self { - Self(Some(component_clone_via_clone::)) + pub fn clone() -> Self { + Self::Custom(component_clone_via_clone::) } /// Set clone handler based on `Reflect` trait. #[cfg(feature = "bevy_reflect")] - pub fn reflect_handler() -> Self { - Self(Some(component_clone_via_reflect)) - } - - /// Set a custom handler for the component. - pub fn custom_handler(handler: ComponentCloneFn) -> Self { - Self(Some(handler)) - } - - /// Get [`ComponentCloneFn`] representing this handler or `None` if set to default handler. - pub fn get_handler(&self) -> Option { - self.0 - } -} - -/// A registry of component clone handlers. Allows to set global default and per-component clone function for all components in the world. -#[derive(Debug)] -pub struct ComponentCloneHandlers { - handlers: Vec>, - default_handler: ComponentCloneFn, -} - -impl ComponentCloneHandlers { - /// Sets the default handler for this registry. All components with [`default`](ComponentCloneHandler::default_handler) handler, as well as any component that does not have an - /// explicitly registered clone function will use this handler. - /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn set_default_handler(&mut self, handler: ComponentCloneFn) { - self.default_handler = handler; - } - - /// Returns the currently registered default handler. - pub fn get_default_handler(&self) -> ComponentCloneFn { - self.default_handler - } - - /// Sets a handler for a specific component. - /// - /// See [Handlers section of `EntityCloneBuilder`](crate::entity::EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn set_component_handler(&mut self, id: ComponentId, handler: ComponentCloneHandler) { - if id.0 >= self.handlers.len() { - self.handlers.resize(id.0 + 1, None); - } - self.handlers[id.0] = handler.0; - } - - /// Checks if the specified component is registered. If not, the component will use the default global handler. - /// - /// This will return an incorrect result if `id` did not come from the same world as `self`. - pub fn is_handler_registered(&self, id: ComponentId) -> bool { - self.handlers.get(id.0).is_some_and(Option::is_some) - } - - /// Gets a handler to clone a component. This can be one of the following: - /// - Custom clone function for this specific component. - /// - Default global handler. - /// - A [`component_clone_ignore`] (no cloning). - /// - /// This will return an incorrect result if `id` did not come from the same world as `self`. - pub fn get_handler(&self, id: ComponentId) -> ComponentCloneFn { - match self.handlers.get(id.0) { - Some(Some(handler)) => *handler, - Some(None) | None => self.default_handler, - } - } -} - -impl Default for ComponentCloneHandlers { - fn default() -> Self { - Self { - handlers: Default::default(), - #[cfg(feature = "bevy_reflect")] - default_handler: component_clone_via_reflect, - #[cfg(not(feature = "bevy_reflect"))] - default_handler: component_clone_ignore, + pub fn reflect() -> Self { + Self::Custom(component_clone_via_reflect) + } + + /// Returns the "global default" + pub fn global_default_fn() -> ComponentCloneFn { + #[cfg(feature = "bevy_reflect")] + return component_clone_via_reflect; + #[cfg(not(feature = "bevy_reflect"))] + return component_clone_ignore; + } + + /// Resolves the [`ComponentCloneBehavior`] to a [`ComponentCloneFn`]. If [`ComponentCloneBehavior::Default`] is + /// specified, the given `default` function will be used. + pub fn resolve(&self, default: ComponentCloneFn) -> ComponentCloneFn { + match self { + ComponentCloneBehavior::Default => default, + ComponentCloneBehavior::Ignore => component_clone_ignore, + ComponentCloneBehavior::Custom(custom) + | ComponentCloneBehavior::RelationshipTarget(custom) => *custom, } } } @@ -1129,7 +1144,6 @@ pub struct Components { components: Vec, indices: TypeIdMap, resource_indices: TypeIdMap, - component_clone_handlers: ComponentCloneHandlers, } impl Components { @@ -1142,14 +1156,13 @@ impl Components { /// * [`Components::component_id()`] /// * [`Components::register_component_with_descriptor()`] #[inline] - pub fn register_component(&mut self, storages: &mut Storages) -> ComponentId { - self.register_component_internal::(storages, &mut Vec::new()) + pub fn register_component(&mut self) -> ComponentId { + self.register_component_internal::(&mut Vec::new()) } #[inline] fn register_component_internal( &mut self, - storages: &mut Storages, recursion_check_stack: &mut Vec, ) -> ComponentId { let mut is_new_registration = false; @@ -1163,7 +1176,6 @@ impl Components { *indices.entry(type_id).or_insert_with(|| { let id = Components::register_component_inner( components, - storages, ComponentDescriptor::new::(), ); is_new_registration = true; @@ -1175,17 +1187,20 @@ impl Components { T::register_required_components( id, self, - storages, &mut required_components, 0, recursion_check_stack, ); let info = &mut self.components[id.index()]; + + #[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.required_components = required_components; - let clone_handler = T::get_component_clone_handler(); - self.component_clone_handlers - .set_component_handler(id, clone_handler); } id } @@ -1203,23 +1218,18 @@ impl Components { /// * [`Components::register_component()`] pub fn register_component_with_descriptor( &mut self, - storages: &mut Storages, descriptor: ComponentDescriptor, ) -> ComponentId { - Components::register_component_inner(&mut self.components, storages, descriptor) + Components::register_component_inner(&mut self.components, descriptor) } #[inline] fn register_component_inner( components: &mut Vec, - storages: &mut Storages, descriptor: ComponentDescriptor, ) -> ComponentId { let component_id = ComponentId(components.len()); let info = ComponentInfo::new(component_id, descriptor); - if info.descriptor.storage_type == StorageType::SparseSet { - storages.sparse_sets.get_or_insert(&info); - } components.push(info); component_id } @@ -1454,14 +1464,13 @@ impl Components { #[doc(hidden)] pub fn register_required_components_manual( &mut self, - storages: &mut Storages, required_components: &mut RequiredComponents, constructor: fn() -> R, inheritance_depth: u16, recursion_check_stack: &mut Vec, ) { - let requiree = self.register_component_internal::(storages, recursion_check_stack); - let required = self.register_component_internal::(storages, recursion_check_stack); + let requiree = self.register_component_internal::(recursion_check_stack); + let required = self.register_component_internal::(recursion_check_stack); // SAFETY: We just created the components. unsafe { @@ -1548,16 +1557,6 @@ impl Components { .map(|info| &mut info.required_by) } - /// Retrieves the [`ComponentCloneHandlers`]. Can be used to get clone functions for components. - pub fn get_component_clone_handlers(&self) -> &ComponentCloneHandlers { - &self.component_clone_handlers - } - - /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. - pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { - &mut self.component_clone_handlers - } - /// Type-erased equivalent of [`Components::component_id()`]. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { @@ -1936,17 +1935,9 @@ pub enum RequiredComponentsError { } /// A Required Component constructor. See [`Component`] for details. -#[cfg(feature = "track_location")] #[derive(Clone)] pub struct RequiredComponentConstructor( - pub Arc)>, -); - -/// A Required Component constructor. See [`Component`] for details. -#[cfg(not(feature = "track_location"))] -#[derive(Clone)] -pub struct RequiredComponentConstructor( - pub Arc, + pub Arc, ); impl RequiredComponentConstructor { @@ -1966,17 +1957,9 @@ impl RequiredComponentConstructor { change_tick: Tick, table_row: TableRow, entity: Entity, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { - (self.0)( - table, - sparse_sets, - change_tick, - table_row, - entity, - #[cfg(feature = "track_location")] - caller, - ); + (self.0)(table, sparse_sets, change_tick, table_row, entity, caller); } } @@ -2053,11 +2036,10 @@ impl RequiredComponents { pub fn register( &mut self, components: &mut Components, - storages: &mut Storages, constructor: fn() -> C, inheritance_depth: u16, ) { - let component_id = components.register_component::(storages); + let component_id = components.register_component::(); self.register_by_id(component_id, constructor, inheritance_depth); } @@ -2081,20 +2063,15 @@ impl RequiredComponents { #[cfg(feature = "portable-atomic")] use alloc::boxed::Box; - #[cfg(feature = "track_location")] type Constructor = dyn for<'a, 'b> Fn( &'a mut Table, &'b mut SparseSets, Tick, TableRow, Entity, - &'static Location<'static>, + MaybeLocation, ); - #[cfg(not(feature = "track_location"))] - type Constructor = - dyn for<'a, 'b> Fn(&'a mut Table, &'b mut SparseSets, Tick, TableRow, Entity); - #[cfg(feature = "portable-atomic")] type Intermediate = Box; @@ -2102,12 +2079,7 @@ impl RequiredComponents { type Intermediate = Arc; let boxed: Intermediate = Intermediate::new( - move |table, - sparse_sets, - change_tick, - table_row, - entity, - #[cfg(feature = "track_location")] caller| { + move |table, sparse_sets, change_tick, table_row, entity, caller| { OwningPtr::make(constructor(), |ptr| { // SAFETY: This will only be called in the context of `BundleInfo::write_components`, which will // pass in a valid table_row and entity requiring a C constructor @@ -2123,7 +2095,6 @@ impl RequiredComponents { component_id, C::STORAGE_TYPE, ptr, - #[cfg(feature = "track_location")] caller, ); } @@ -2201,12 +2172,11 @@ pub fn enforce_no_required_components_recursion( } /// Component [clone handler function](ComponentCloneFn) implemented using the [`Clone`] trait. -/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for the specific component it is implemented for. +/// Can be [set](Component::clone_behavior) as clone handler for the specific component it is implemented for. /// It will panic if set as handler for any other component. /// -/// See [`ComponentCloneHandlers`] for more details. pub fn component_clone_via_clone( - _world: &mut DeferredWorld, + _commands: &mut Commands, ctx: &mut ComponentCloneCtx, ) { if let Some(component) = ctx.read_source_component::() { @@ -2215,7 +2185,7 @@ pub fn component_clone_via_clone( } /// Component [clone handler function](ComponentCloneFn) implemented using reflect. -/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for any registered component, +/// Can be [set](Component::clone_behavior) as clone handler for any registered component, /// but only reflected components will be cloned. /// /// To clone a component using this handler, the following must be true: @@ -2228,10 +2198,10 @@ pub fn component_clone_via_clone( /// /// If any of the conditions is not satisfied, the component will be skipped. /// -/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for details. +/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. #[cfg(feature = "bevy_reflect")] -pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let Some(registry) = ctx.type_registry() else { +pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentCloneCtx) { + let Some(app_registry) = ctx.type_registry().cloned() else { return; }; let Some(source_component_reflect) = ctx.read_source_component_reflect() else { @@ -2240,16 +2210,24 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen let component_info = ctx.component_info(); // checked in read_source_component_reflect let type_id = component_info.type_id().unwrap(); - let registry = registry.read(); + let registry = app_registry.read(); // Try to clone using ReflectFromReflect if let Some(reflect_from_reflect) = registry.get_type_data::(type_id) { - if let Some(component) = + if let Some(mut component) = reflect_from_reflect.from_reflect(source_component_reflect.as_partial_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); + }); + } drop(registry); + ctx.write_target_component_reflect(component); return; } @@ -2269,14 +2247,36 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen 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.clone_value(); let component_layout = component_info.layout(); let target = ctx.target(); let component_id = ctx.component_id(); - world.commands().queue(move |world: &mut World| { + for entity in mapped_entities.iter_mut() { + *entity = ctx.entity_mapper().get_mapped(*entity); + } + drop(registry); + commands.queue(move |world: &mut World| { let mut component = reflect_from_world.from_world(world); assert_eq!(type_id, (*component).type_id()); component.apply(source_component_cloned.as_partial_reflect()); + if let Some(reflect_component) = app_registry + .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; + }); + } // SAFETY: // - component_id is from the same world as target entity // - component is a valid value represented by component_id @@ -2294,14 +2294,14 @@ pub fn component_clone_via_reflect(world: &mut DeferredWorld, ctx: &mut Componen /// Noop implementation of component clone handler function. /// -/// See [`EntityCloneBuilder`](crate::entity::EntityCloneBuilder) for details. -pub fn component_clone_ignore(_world: &mut DeferredWorld, _ctx: &mut ComponentCloneCtx) {} +/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details. +pub fn component_clone_ignore(_commands: &mut Commands, _ctx: &mut ComponentCloneCtx) {} /// Wrapper for components clone specialization using autoderef. #[doc(hidden)] -pub struct ComponentCloneSpecializationWrapper(PhantomData); +pub struct DefaultCloneBehaviorSpecialization(PhantomData); -impl Default for ComponentCloneSpecializationWrapper { +impl Default for DefaultCloneBehaviorSpecialization { fn default() -> Self { Self(PhantomData) } @@ -2309,22 +2309,22 @@ impl Default for ComponentCloneSpecializationWrapper { /// Base trait for components clone specialization using autoderef. #[doc(hidden)] -pub trait ComponentCloneBase { - fn get_component_clone_handler(&self) -> ComponentCloneHandler; +pub trait DefaultCloneBehaviorBase { + fn default_clone_behavior(&self) -> ComponentCloneBehavior; } -impl ComponentCloneBase for ComponentCloneSpecializationWrapper { - fn get_component_clone_handler(&self) -> ComponentCloneHandler { - ComponentCloneHandler::default_handler() +impl DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization { + fn default_clone_behavior(&self) -> ComponentCloneBehavior { + ComponentCloneBehavior::Default } } /// Specialized trait for components clone specialization using autoderef. #[doc(hidden)] -pub trait ComponentCloneViaClone { - fn get_component_clone_handler(&self) -> ComponentCloneHandler; +pub trait DefaultCloneBehaviorViaClone { + fn default_clone_behavior(&self) -> ComponentCloneBehavior; } -impl ComponentCloneViaClone for &ComponentCloneSpecializationWrapper { - fn get_component_clone_handler(&self) -> ComponentCloneHandler { - ComponentCloneHandler::clone_handler::() +impl DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization { + fn default_clone_behavior(&self) -> ComponentCloneBehavior { + ComponentCloneBehavior::clone::() } } diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index b337b3c079d33..389f30ff8d7b2 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1,6 +1,5 @@ use alloc::{borrow::ToOwned, vec::Vec}; use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_platform_support::sync::Arc; use bevy_ptr::{Ptr, PtrMut}; use bumpalo::Bump; use core::{any::TypeId, ptr::NonNull}; @@ -8,14 +7,19 @@ use core::{any::TypeId, ptr::NonNull}; #[cfg(feature = "bevy_reflect")] use alloc::boxed::Box; +use crate::component::{ComponentCloneBehavior, ComponentCloneFn}; +use crate::entity::hash_map::EntityHashMap; +use crate::entity::EntityMapper; +use crate::system::Commands; use crate::{ bundle::Bundle, - component::{Component, ComponentCloneHandler, ComponentId, ComponentInfo, Components}, + component::{Component, ComponentId, ComponentInfo, Components}, entity::Entity, - hierarchy::{ChildOf, Children}, query::DebugCheckedUnwrap, - world::{DeferredWorld, World}, + world::World, }; +use alloc::collections::VecDeque; +use core::cell::RefCell; /// Context for component clone handlers. /// @@ -25,16 +29,19 @@ pub struct ComponentCloneCtx<'a, 'b> { component_id: ComponentId, source_component_ptr: Ptr<'a>, target_component_written: bool, - target_components_ptrs: &'a mut Vec>, - target_components_buffer: &'b Bump, + bundle_scratch: &'a mut BundleScratch<'b>, + bundle_scratch_allocator: &'b Bump, + source: Entity, + target: Entity, components: &'a Components, component_info: &'a ComponentInfo, - entity_cloner: &'a EntityCloner, + entity_cloner: &'a mut EntityCloner, + mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, #[cfg(not(feature = "bevy_reflect"))] #[expect(dead_code)] - type_registry: Option<()>, + type_registry: Option<&'a ()>, } impl<'a, 'b> ComponentCloneCtx<'a, 'b> { @@ -46,21 +53,27 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// - `source_component_ptr` points to a valid component of type represented by `component_id`. unsafe fn new( component_id: ComponentId, + source: Entity, + target: Entity, source_component_ptr: Ptr<'a>, - target_components_ptrs: &'a mut Vec>, - target_components_buffer: &'b Bump, + bundle_scratch_allocator: &'b Bump, + bundle_scratch: &'a mut BundleScratch<'b>, components: &'a Components, - entity_cloner: &'a EntityCloner, + entity_cloner: &'a mut EntityCloner, + mapper: &'a mut dyn EntityMapper, #[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>, - #[cfg(not(feature = "bevy_reflect"))] type_registry: Option<()>, + #[cfg(not(feature = "bevy_reflect"))] type_registry: Option<&'a ()>, ) -> Self { Self { component_id, + source, + target, source_component_ptr, - target_components_ptrs, + bundle_scratch, target_component_written: false, - target_components_buffer, + bundle_scratch_allocator, components, + mapper, component_info: components.get_info_unchecked(component_id), entity_cloner, type_registry, @@ -74,12 +87,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// Returns the current source entity. pub fn source(&self) -> Entity { - self.entity_cloner.source + self.source } /// Returns the current target entity. pub fn target(&self) -> Entity { - self.entity_cloner.target + self.target } /// Returns the [`ComponentId`] of the component being cloned. @@ -92,6 +105,19 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { self.component_info } + /// Returns true if the [`EntityCloner`] is configured to recursively clone entities. When this is enabled, + /// entities stored in a cloned entity's [`RelationshipTarget`](crate::relationship::RelationshipTarget) component with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) will also be cloned. + #[inline] + pub fn is_recursive(&self) -> bool { + self.entity_cloner.is_recursive + } + + /// Returns this context's [`EntityMapper`]. + pub fn entity_mapper(&mut self) -> &mut dyn EntityMapper { + self.mapper + } + /// Returns a reference to the component on the source entity. /// /// Will return `None` if `ComponentId` of requested component does not match `ComponentId` of source component @@ -137,21 +163,26 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// - Component has already been written once. /// - 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, component: T) { - let short_name = disqualified::ShortName::of::(); + pub fn write_target_component(&mut self, mut component: C) { + C::visit_entities_mut(&mut component, |entity| { + *entity = self.mapper.get_mapped(*entity); + }); + let short_name = disqualified::ShortName::of::(); if self.target_component_written { panic!("Trying to write component '{short_name}' multiple times") } if self .component_info .type_id() - .is_none_or(|id| id != TypeId::of::()) + .is_none_or(|id| id != TypeId::of::()) { panic!("TypeId of component '{short_name}' does not match source component TypeId") }; - let component_ref = self.target_components_buffer.alloc(component); - self.target_components_ptrs - .push(PtrMut::from(component_ref)); + // SAFETY: the TypeId of self.component_id has been checked to ensure it matches `C` + unsafe { + self.bundle_scratch + .push(self.bundle_scratch_allocator, self.component_id, component); + }; self.target_component_written = true; } @@ -175,11 +206,11 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { panic!("Trying to write component multiple times") } let layout = self.component_info.layout(); - let target_component_data_ptr = self.target_components_buffer.alloc_layout(layout); + let target_component_data_ptr = self.bundle_scratch_allocator.alloc_layout(layout); if clone_fn(self.source_component_ptr, target_component_data_ptr) { - self.target_components_ptrs - .push(PtrMut::new(target_component_data_ptr)); + self.bundle_scratch + .push_ptr(self.component_id, PtrMut::new(target_component_data_ptr)); self.target_component_written = true; } } @@ -210,7 +241,7 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { let component_data_ptr = Box::into_raw(component).cast::(); let target_component_data_ptr = - self.target_components_buffer.alloc_layout(component_layout); + self.bundle_scratch_allocator.alloc_layout(component_layout); // SAFETY: // - target_component_data_ptr and component_data have the same data type. // - component_data_ptr has layout of component_layout @@ -220,34 +251,14 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { target_component_data_ptr.as_ptr(), component_layout.size(), ); - self.target_components_ptrs - .push(PtrMut::new(target_component_data_ptr)); + self.bundle_scratch + .push_ptr(self.component_id, PtrMut::new(target_component_data_ptr)); alloc::alloc::dealloc(component_data_ptr, component_layout); } self.target_component_written = true; } - /// Return a reference to this context's `EntityCloner` instance. - /// - /// This can be used to issue clone commands using the same cloning configuration: - /// ``` - /// # use bevy_ecs::world::{DeferredWorld, World}; - /// # use bevy_ecs::entity::ComponentCloneCtx; - /// fn clone_handler(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - /// let another_target = world.commands().spawn_empty().id(); - /// let mut entity_cloner = ctx - /// .entity_cloner() - /// .with_source_and_target(ctx.source(), another_target); - /// world.commands().queue(move |world: &mut World| { - /// entity_cloner.clone_entity(world); - /// }); - /// } - /// ``` - pub fn entity_cloner(&self) -> &EntityCloner { - self.entity_cloner - } - /// Returns instance of [`Components`]. pub fn components(&self) -> &Components { self.components @@ -260,138 +271,24 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { pub fn type_registry(&self) -> Option<&crate::reflect::AppTypeRegistry> { self.type_registry } -} - -/// A helper struct to clone an entity. Used internally by [`EntityCloneBuilder::clone_entity`]. -pub struct EntityCloner { - source: Entity, - target: Entity, - filter_allows_components: bool, - filter: Arc>, - clone_handlers_overrides: Arc>, - move_components: bool, -} - -impl EntityCloner { - /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. - #[track_caller] - pub fn clone_entity(&mut self, world: &mut World) { - // SAFETY: - // - `source_entity` is read-only. - // - `type_registry` is read-only. - // - `components` is read-only. - // - `deferred_world` disallows structural ecs changes, which means all read-only resources above a not affected. - let (type_registry, source_entity, components, mut deferred_world) = unsafe { - let world = world.as_unsafe_world_cell(); - let source_entity = world - .get_entity(self.source) - .expect("Source entity must exist"); - - #[cfg(feature = "bevy_reflect")] - let app_registry = world.get_resource::(); - #[cfg(not(feature = "bevy_reflect"))] - let app_registry = Option::<()>::None; - - ( - app_registry, - source_entity, - world.components(), - world.into_deferred(), - ) - }; - let archetype = source_entity.archetype(); - - let component_data = Bump::new(); - let mut component_ids: Vec = Vec::with_capacity(archetype.component_count()); - let mut component_data_ptrs: Vec = Vec::with_capacity(archetype.component_count()); - - for component in archetype.components() { - if !self.is_cloning_allowed(&component) { - continue; - } - - let global_handlers = components.get_component_clone_handlers(); - let handler = match self.clone_handlers_overrides.get(&component) { - Some(handler) => handler - .get_handler() - .unwrap_or_else(|| global_handlers.get_default_handler()), - None => global_handlers.get_handler(component), - }; - - // SAFETY: - // - There are no other mutable references to source entity. - // - `component` is from `source_entity`'s archetype - let source_component_ptr = - unsafe { source_entity.get_by_id(component).debug_checked_unwrap() }; - - // SAFETY: - // - `components` and `component` are from the same world - // - `source_component_ptr` is valid and points to the same type as represented by `component` - let mut ctx = unsafe { - ComponentCloneCtx::new( - component, - source_component_ptr, - &mut component_data_ptrs, - &component_data, - components, - self, - type_registry, - ) - }; - - (handler)(&mut deferred_world, &mut ctx); - - if ctx.target_component_written { - component_ids.push(component); - } - } - - world.flush(); - - if !world.entities.contains(self.target) { - panic!("Target entity does not exist"); - } - - debug_assert_eq!(component_data_ptrs.len(), component_ids.len()); - - // SAFETY: - // - All `component_ids` are from the same world as `target` entity - // - All `component_data_ptrs` are valid types represented by `component_ids` - unsafe { - world.entity_mut(self.target).insert_by_ids( - &component_ids, - component_data_ptrs.into_iter().map(|ptr| ptr.promote()), - ); - } - - if self.move_components { - world.entity_mut(self.source).remove_by_ids(&component_ids); - } - } - - fn is_cloning_allowed(&self, component: &ComponentId) -> bool { - (self.filter_allows_components && self.filter.contains(component)) - || (!self.filter_allows_components && !self.filter.contains(component)) - } - /// Reuse existing [`EntityCloner`] configuration with new source and target. - pub fn with_source_and_target(&self, source: Entity, target: Entity) -> EntityCloner { - EntityCloner { - source, - target, - filter: self.filter.clone(), - clone_handlers_overrides: self.clone_handlers_overrides.clone(), - ..*self - } + /// Queues the `entity` to be cloned by the current [`EntityCloner`] + pub fn queue_entity_clone(&self, entity: Entity) { + self.entity_cloner + .clone_queue + .borrow_mut() + .push_back(entity); } } -/// Builder struct to clone an entity. Allows configuring which components to clone, as well as how to clone them. +/// A configuration determining how to clone entities. This can be built using [`EntityCloner::build`], which +/// returns an [`EntityClonerBuilder`]. +/// /// After configuration is complete an entity can be cloned using [`Self::clone_entity`]. /// ///``` /// use bevy_ecs::prelude::*; -/// use bevy_ecs::entity::EntityCloneBuilder; +/// use bevy_ecs::entity::EntityCloner; /// /// #[derive(Component, Clone, PartialEq, Eq)] /// struct A { @@ -405,7 +302,7 @@ impl EntityCloner { /// let entity = world.spawn(component.clone()).id(); /// let entity_clone = world.spawn_empty().id(); /// -/// EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone); +/// EntityCloner::build(&mut world).clone_entity(entity, entity_clone); /// /// assert!(world.get::(entity_clone).is_some_and(|c| *c == component)); ///``` @@ -416,77 +313,293 @@ impl EntityCloner { /// /// It should be noted that if `Component` is implemented manually or if `Clone` implementation is conditional /// (like when deriving `Clone` for a type with a generic parameter without `Clone` bound), -/// the component will be cloned using the [default cloning strategy](crate::component::ComponentCloneHandlers::get_default_handler). -/// To use `Clone`-based handler ([`ComponentCloneHandler::clone_handler`]) in this case it should be set manually using one -/// of the methods mentioned in the [Handlers](#handlers) section +/// the component will be cloned using the [default cloning strategy](crate::component::ComponentCloneBehavior::global_default_fn). +/// To use `Clone`-based handler ([`ComponentCloneBehavior::clone`]) in this case it should be set manually using one +/// of the methods mentioned in the [Clone Behaviors](#Clone-Behaviors) section /// -/// Here's an example of how to do it using [`get_component_clone_handler`](Component::get_component_clone_handler): +/// Here's an example of how to do it using [`clone_behavior`](Component::clone_behavior): /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::component::{StorageType, component_clone_via_clone, ComponentCloneHandler, Mutable}; +/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable}; /// #[derive(Clone)] /// struct SomeComponent; /// /// impl Component for SomeComponent { /// const STORAGE_TYPE: StorageType = StorageType::Table; /// type Mutability = Mutable; -/// fn get_component_clone_handler() -> ComponentCloneHandler { -/// ComponentCloneHandler::clone_handler::() +/// fn clone_behavior() -> ComponentCloneBehavior { +/// ComponentCloneBehavior::clone::() /// } /// } /// ``` /// -/// # Handlers -/// `EntityCloneBuilder` clones entities by cloning components using [`handlers`](ComponentCloneHandler), and there are multiple layers +/// # Clone Behaviors +/// [`EntityCloner`] clones entities by cloning components using [`ComponentCloneBehavior`], and there are multiple layers /// to decide which handler to use for which component. The overall hierarchy looks like this (priority from most to least): -/// 1. local overrides using [`override_component_clone_handler`](Self::override_component_clone_handler) -/// 2. global overrides using [`set_component_handler`](crate::component::ComponentCloneHandlers::set_component_handler) -/// 3. component-defined handler using [`get_component_clone_handler`](Component::get_component_clone_handler) -/// 4. default handler override using [`set_default_handler`](crate::component::ComponentCloneHandlers::set_default_handler) -/// 5. reflect-based or noop default clone handler depending on if `bevy_reflect` feature is enabled or not. +/// 1. local overrides using [`EntityClonerBuilder::override_clone_behavior`] +/// 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 EntityCloneBuilder<'w> { - world: &'w mut World, +pub struct EntityCloner { filter_allows_components: bool, filter: HashSet, - clone_handlers_overrides: HashMap, - attach_required_components: bool, + clone_behavior_overrides: HashMap, move_components: bool, + is_recursive: bool, + default_clone_fn: ComponentCloneFn, + clone_queue: RefCell>, } -impl<'w> EntityCloneBuilder<'w> { - /// Creates a new [`EntityCloneBuilder`] for world. - pub fn new(world: &'w mut World) -> Self { +impl Default for EntityCloner { + fn default() -> Self { Self { - world, filter_allows_components: false, filter: Default::default(), - clone_handlers_overrides: Default::default(), - attach_required_components: true, + clone_behavior_overrides: Default::default(), move_components: false, + is_recursive: false, + default_clone_fn: ComponentCloneBehavior::global_default_fn(), + clone_queue: Default::default(), + } + } +} + +/// An expandable scratch space for defining a dynamic bundle. +struct BundleScratch<'a> { + component_ids: Vec, + component_ptrs: Vec>, +} + +impl<'a> BundleScratch<'a> { + pub(crate) fn with_capacity(capacity: usize) -> Self { + Self { + component_ids: Vec::with_capacity(capacity), + component_ptrs: Vec::with_capacity(capacity), + } + } + + /// Pushes the `ptr` component onto this storage with the given `id` [`ComponentId`]. + /// + /// # Safety + /// The `id` [`ComponentId`] must match the component `ptr` for whatever [`World`] this scratch will + /// be written to. `ptr` must contain valid uniquely-owned data that matches the type of component referenced + /// in `id`. + pub(crate) unsafe fn push_ptr(&mut self, id: ComponentId, ptr: PtrMut<'a>) { + self.component_ids.push(id); + self.component_ptrs.push(ptr); + } + + /// Pushes the `C` component onto this storage with the given `id` [`ComponentId`], using the given `bump` allocator. + /// + /// # Safety + /// The `id` [`ComponentId`] must match the component `C` for whatever [`World`] this scratch will + /// be written to. + pub(crate) unsafe fn push( + &mut self, + allocator: &'a Bump, + id: ComponentId, + component: C, + ) { + let component_ref = allocator.alloc(component); + self.component_ids.push(id); + self.component_ptrs.push(PtrMut::from(component_ref)); + } + + /// Writes the scratch components to the given entity in the given world. + /// + /// # Safety + /// All [`ComponentId`] values in this instance must come from `world`. + pub(crate) unsafe fn write(self, world: &mut World, entity: Entity) { + // SAFETY: + // - All `component_ids` are from the same world as `target` entity + // - All `component_data_ptrs` are valid types represented by `component_ids` + unsafe { + world.entity_mut(entity).insert_by_ids( + &self.component_ids, + self.component_ptrs.into_iter().map(|ptr| ptr.promote()), + ); } } +} - /// Finishes configuring the builder and clones `source` entity to `target`. - pub fn clone_entity(self, source: Entity, target: Entity) { - let EntityCloneBuilder { +impl EntityCloner { + /// Returns a new [`EntityClonerBuilder`] using the given `world`. + pub fn build(world: &mut World) -> EntityClonerBuilder { + EntityClonerBuilder { world, - filter_allows_components, - filter, - clone_handlers_overrides, - move_components, - .. - } = self; - - EntityCloner { - source, - target, - filter_allows_components, - filter: Arc::new(filter), - clone_handlers_overrides: Arc::new(clone_handlers_overrides), - move_components, + attach_required_components: true, + entity_cloner: EntityCloner::default(), + } + } + + /// Returns `true` if this cloner is configured to clone entities recursively. + #[inline] + pub fn is_recursive(&self) -> bool { + self.is_recursive + } + + /// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration. + fn clone_entity_internal( + &mut self, + world: &mut World, + source: Entity, + mapper: &mut dyn EntityMapper, + ) -> Entity { + let target = mapper.get_mapped(source); + // PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`. + let bundle_scratch_allocator = Bump::new(); + let mut bundle_scratch: BundleScratch; + { + let world = world.as_unsafe_world_cell(); + let source_entity = world.get_entity(source).expect("Source entity must exist"); + + #[cfg(feature = "bevy_reflect")] + // SAFETY: we have unique access to `world`, nothing else accesses the registry at this moment, and we clone + // the registry, which prevents future conflicts. + let app_registry = unsafe { + world + .get_resource::() + .cloned() + }; + #[cfg(not(feature = "bevy_reflect"))] + let app_registry = Option::<()>::None; + + 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) { + continue; + } + + let handler = match self.clone_behavior_overrides.get(&component) { + Some(clone_behavior) => clone_behavior.resolve(self.default_clone_fn), + None => world + .components() + .get_info(component) + .map(|info| info.clone_behavior().resolve(self.default_clone_fn)) + .unwrap_or(self.default_clone_fn), + }; + + // SAFETY: + // - There are no other mutable references to source entity. + // - `component` is from `source_entity`'s archetype + let source_component_ptr = + unsafe { source_entity.get_by_id(component).debug_checked_unwrap() }; + + // SAFETY: + // - `components` and `component` are from the same world + // - `source_component_ptr` is valid and points to the same type as represented by `component` + let mut ctx = unsafe { + ComponentCloneCtx::new( + component, + source, + target, + source_component_ptr, + &bundle_scratch_allocator, + &mut bundle_scratch, + world.components(), + self, + mapper, + app_registry.as_ref(), + ) + }; + + (handler)(&mut commands, &mut ctx); + } + } + + world.flush(); + + if !world.entities.contains(target) { + panic!("Target entity does not exist"); + } + + if self.move_components { + world + .entity_mut(source) + .remove_by_ids(&bundle_scratch.component_ids); + } + + // SAFETY: + // - All `component_ids` are from the same world as `target` entity + // - All `component_data_ptrs` are valid types represented by `component_ids` + unsafe { bundle_scratch.write(world, target) }; + target + } + + /// Clones and inserts components from the `source` entity into `target` entity using the stored configuration. + /// If this [`EntityCloner`] has [`EntityCloner::is_recursive`], then it will recursively spawn entities as defined + /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) + #[track_caller] + pub fn clone_entity(&mut self, world: &mut World, source: Entity, target: Entity) { + let mut map = EntityHashMap::::new(); + map.set_mapped(source, target); + self.clone_entity_mapped(world, source, &mut map); + } + + /// Clones and inserts components from the `source` entity into a newly spawned entity using the stored configuration. + /// If this [`EntityCloner`] has [`EntityCloner::is_recursive`], then it will recursively spawn entities as defined + /// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) + #[track_caller] + pub fn spawn_clone(&mut self, world: &mut World, source: Entity) -> Entity { + let target = world.spawn_empty().id(); + self.clone_entity(world, source, target); + target + } + + /// Clones the entity into whatever entity `mapper` chooses for it. + #[track_caller] + pub fn clone_entity_mapped( + &mut self, + world: &mut World, + source: Entity, + mapper: &mut dyn EntityMapper, + ) -> Entity { + let target = self.clone_entity_internal(world, source, mapper); + loop { + let queued = self.clone_queue.borrow_mut().pop_front(); + if let Some(queued) = queued { + let target = world.entities.reserve_entity(); + mapper.set_mapped(queued, target); + self.clone_entity_internal(world, queued, mapper); + } else { + break; + } } - .clone_entity(world); + target + } + + fn is_cloning_allowed(&self, component: &ComponentId) -> bool { + (self.filter_allows_components && self.filter.contains(component)) + || (!self.filter_allows_components && !self.filter.contains(component)) + } +} + +/// A builder for configuring [`EntityCloner`]. See [`EntityCloner`] for more information. +#[derive(Debug)] +pub struct EntityClonerBuilder<'w> { + world: &'w mut World, + entity_cloner: EntityCloner, + attach_required_components: bool, +} + +impl<'w> EntityClonerBuilder<'w> { + /// Internally calls [`EntityCloner::clone_entity`] on the builder's [`World`]. + pub fn clone_entity(&mut self, source: Entity, target: Entity) -> &mut Self { + self.entity_cloner.clone_entity(self.world, source, target); + self + } + /// Finishes configuring [`EntityCloner`] returns it. + pub fn finish(self) -> EntityCloner { + self.entity_cloner } /// By default, any components allowed/denied through the filter will automatically @@ -496,7 +609,7 @@ impl<'w> EntityCloneBuilder<'w> { /// will not involve required components. pub fn without_required_components( &mut self, - builder: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + builder: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.attach_required_components = false; builder(self); @@ -504,15 +617,21 @@ impl<'w> EntityCloneBuilder<'w> { self } + /// Sets the default clone function to use. + pub fn with_default_clone_fn(&mut self, clone_fn: ComponentCloneFn) -> &mut Self { + self.entity_cloner.default_clone_fn = clone_fn; + self + } + /// Sets whether the cloner should remove any components that were cloned, /// effectively moving them from the source entity to the target. /// /// This is disabled by default. /// /// The setting only applies to components that are allowed through the filter - /// at the time [`EntityCloneBuilder::clone_entity`] is called. + /// at the time [`EntityClonerBuilder::clone_entity`] is called. pub fn move_components(&mut self, enable: bool) -> &mut Self { - self.move_components = enable; + self.entity_cloner.move_components = enable; self } @@ -555,8 +674,8 @@ impl<'w> EntityCloneBuilder<'w> { /// Resets the filter to allow all components to be cloned. pub fn allow_all(&mut self) -> &mut Self { - self.filter_allows_components = false; - self.filter.clear(); + self.entity_cloner.filter_allows_components = false; + self.entity_cloner.filter.clear(); self } @@ -590,70 +709,82 @@ impl<'w> EntityCloneBuilder<'w> { /// Sets the filter to deny all components. pub fn deny_all(&mut self) -> &mut Self { - self.filter_allows_components = true; - self.filter.clear(); + self.entity_cloner.filter_allows_components = true; + self.entity_cloner.filter.clear(); self } - /// Overrides the [`ComponentCloneHandler`] for a component in this builder. - /// This handler will be used to clone the component instead of the global one defined by [`ComponentCloneHandlers`](crate::component::ComponentCloneHandlers) + /// Overrides the [`ComponentCloneBehavior`] for a component in this builder. + /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. /// - /// See [Handlers section of `EntityCloneBuilder`](EntityCloneBuilder#handlers) to understand how this affects handler priority. - pub fn override_component_clone_handler( + /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. + pub fn override_clone_behavior( &mut self, - handler: ComponentCloneHandler, + clone_behavior: ComponentCloneBehavior, ) -> &mut Self { if let Some(id) = self.world.components().component_id::() { - self.clone_handlers_overrides.insert(id, handler); + self.entity_cloner + .clone_behavior_overrides + .insert(id, clone_behavior); } self } - /// Removes a previously set override of [`ComponentCloneHandler`] for a component in this builder. - pub fn remove_component_clone_handler_override(&mut self) -> &mut Self { + /// Overrides the [`ComponentCloneBehavior`] for a component with the given `component_id` in this builder. + /// This handler will be used to clone the component instead of the global one defined by the [`EntityCloner`]. + /// + /// See [Handlers section of `EntityClonerBuilder`](EntityClonerBuilder#handlers) to understand how this affects handler priority. + pub fn override_clone_behavior_with_id( + &mut self, + component_id: ComponentId, + clone_behavior: ComponentCloneBehavior, + ) -> &mut Self { + self.entity_cloner + .clone_behavior_overrides + .insert(component_id, clone_behavior); + self + } + + /// Removes a previously set override of [`ComponentCloneBehavior`] for a component in this builder. + pub fn remove_clone_behavior_override(&mut self) -> &mut Self { if let Some(id) = self.world.components().component_id::() { - self.clone_handlers_overrides.remove(&id); + self.entity_cloner.clone_behavior_overrides.remove(&id); } self } - /// Sets the option to recursively clone entities. - /// When set to true all children will be cloned with the same options as the parent. - pub fn recursive(&mut self, recursive: bool) -> &mut Self { - if recursive { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_children), - ) - } else { - self.remove_component_clone_handler_override::() - } + /// Removes a previously set override of [`ComponentCloneBehavior`] for a given `component_id` in this builder. + pub fn remove_clone_behavior_override_with_id( + &mut self, + component_id: ComponentId, + ) -> &mut Self { + self.entity_cloner + .clone_behavior_overrides + .remove(&component_id); + self } - /// Sets the option to add cloned entity as a child to the parent entity. - pub fn as_child(&mut self, as_child: bool) -> &mut Self { - if as_child { - self.override_component_clone_handler::(ComponentCloneHandler::custom_handler( - component_clone_parent, - )) - } else { - self.remove_component_clone_handler_override::() - } + /// If `true`, makes the built [`EntityCloner`] recursively clone entities, as defined by + /// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN). + pub fn recursive(&mut self, is_recursive: bool) -> &mut Self { + self.entity_cloner.is_recursive = is_recursive; + self } /// Helper function that allows a component through the filter. fn filter_allow(&mut self, id: ComponentId) { - if self.filter_allows_components { - self.filter.insert(id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.insert(id); } else { - self.filter.remove(&id); + self.entity_cloner.filter.remove(&id); } if self.attach_required_components { if let Some(info) = self.world.components().get_info(id) { for required_id in info.required_components().iter_ids() { - if self.filter_allows_components { - self.filter.insert(required_id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.insert(required_id); } else { - self.filter.remove(&required_id); + self.entity_cloner.filter.remove(&required_id); } } } @@ -662,18 +793,18 @@ impl<'w> EntityCloneBuilder<'w> { /// Helper function that disallows a component through the filter. fn filter_deny(&mut self, id: ComponentId) { - if self.filter_allows_components { - self.filter.remove(&id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.remove(&id); } else { - self.filter.insert(id); + self.entity_cloner.filter.insert(id); } if self.attach_required_components { if let Some(info) = self.world.components().get_info(id) { for required_id in info.required_components().iter_ids() { - if self.filter_allows_components { - self.filter.remove(&required_id); + if self.entity_cloner.filter_allows_components { + self.entity_cloner.filter.remove(&required_id); } else { - self.filter.insert(required_id); + self.entity_cloner.filter.insert(required_id); } } } @@ -681,55 +812,33 @@ impl<'w> EntityCloneBuilder<'w> { } } -/// Clone handler for the [`Children`] component. Allows to clone the entity recursively. -fn component_clone_children(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let children = ctx - .read_source_component::() - .expect("Source entity must have Children component") - .iter(); - let parent = ctx.target(); - for child in children { - let child_clone = world.commands().spawn_empty().id(); - let mut clone_entity = ctx - .entity_cloner() - .with_source_and_target(*child, child_clone); - world.commands().queue(move |world: &mut World| { - clone_entity.clone_entity(world); - world.entity_mut(child_clone).insert(ChildOf(parent)); - }); - } -} - -/// Clone handler for the [`ChildOf`] component. Allows to add clone as a child to the parent entity. -fn component_clone_parent(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { - let parent = ctx - .read_source_component::() - .map(|p| p.0) - .expect("Source entity must have a ChildOf component"); - world - .commands() - .entity(ctx.target()) - .insert(ChildOf(parent)); -} - #[cfg(test)] mod tests { use super::ComponentCloneCtx; use crate::{ - self as bevy_ecs, - component::{Component, ComponentCloneHandler, ComponentDescriptor, StorageType}, - entity::EntityCloneBuilder, - world::{DeferredWorld, World}, + component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, + entity::{hash_map::EntityHashMap, Entity, EntityCloner}, + prelude::{ChildOf, Children, Resource}, + reflect::AppTypeRegistry, + reflect::{ReflectComponent, ReflectFromWorld}, + system::Commands, + world::{FromWorld, World}, }; use alloc::vec::Vec; use bevy_ecs_macros::require; use bevy_ptr::OwningPtr; - use core::alloc::Layout; + use bevy_reflect::Reflect; + use core::{alloc::Layout, ops::Deref}; #[cfg(feature = "bevy_reflect")] mod reflect { use super::*; - use crate::reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}; + use crate::{ + component::{Component, ComponentCloneBehavior}, + entity::EntityCloner, + reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld}, + system::Commands, + }; use alloc::vec; use bevy_reflect::{std_traits::ReflectDefault, FromType, Reflect, ReflectFromPtr}; @@ -747,17 +856,14 @@ mod tests { registry.write().register::(); world.register_component::(); - let id = world.component_id::().unwrap(); - world - .get_component_clone_handlers_mut() - .set_component_handler(id, ComponentCloneHandler::reflect_handler()); - let component = A { field: 5 }; let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); } @@ -798,11 +904,6 @@ mod tests { let a_id = world.register_component::(); let b_id = world.register_component::(); let c_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(c_id, ComponentCloneHandler::reflect_handler()); - let component_a = A { field: 5, field2: vec![1, 2, 3, 4, 5], @@ -819,7 +920,11 @@ mod tests { let e = world.spawn((component_a, component_b, component_c)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior_with_id(a_id, ComponentCloneBehavior::reflect()) + .override_clone_behavior_with_id(b_id, ComponentCloneBehavior::reflect()) + .override_clone_behavior_with_id(c_id, ComponentCloneBehavior::reflect()) + .clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), Some(world.get::(e).unwrap())); assert_eq!(world.get::(e_clone), Some(world.get::(e).unwrap())); @@ -834,7 +939,7 @@ mod tests { #[derive(Component, Reflect)] struct B; - fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) { assert!(ctx.read_source_component_reflect().is_none()); } @@ -850,15 +955,12 @@ mod tests { .insert(>::from_type()); } - let a_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers - .set_component_handler(a_id, ComponentCloneHandler::custom_handler(test_handler)); - let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::Custom(test_handler)) + .clone_entity(e, e_clone); } #[test] @@ -885,7 +987,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert!(world .get::(e_clone) @@ -905,16 +1007,14 @@ mod tests { struct B; let mut world = World::default(); - let a_id = world.register_component::(); - let b_id = world.register_component::(); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler(a_id, ComponentCloneHandler::reflect_handler()); - handlers.set_component_handler(b_id, ComponentCloneHandler::reflect_handler()); // No AppTypeRegistry let e = world.spawn((A, B)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .override_clone_behavior::(ComponentCloneBehavior::reflect()) + .clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), None); assert_eq!(world.get::(e_clone), None); @@ -925,7 +1025,7 @@ mod tests { let e = world.spawn((A, B)).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert_eq!(world.get::(e_clone), None); assert_eq!(world.get::(e_clone), None); } @@ -945,7 +1045,7 @@ mod tests { let e = world.spawn(component.clone()).id(); let e_clone = world.spawn_empty().id(); - EntityCloneBuilder::new(&mut world).clone_entity(e, e_clone); + EntityCloner::build(&mut world).clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); } @@ -967,10 +1067,10 @@ mod tests { let e = world.spawn((component.clone(), B)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -996,9 +1096,9 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny::() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -1025,13 +1125,13 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::(); - builder.allow::(); - builder.allow::(); - builder.deny::(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::() + .allow::() + .allow::() + .deny::() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -1058,11 +1158,11 @@ mod tests { let e = world.spawn((component.clone(), B, C)).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.allow::<(A, B, C)>(); - builder.deny::<(B, C)>(); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .allow::<(A, B, C)>() + .deny::<(B, C)>() + .clone_entity(e, e_clone); assert!(world.get::(e_clone).is_some_and(|c| *c == component)); assert!(world.get::(e_clone).is_none()); @@ -1087,12 +1187,12 @@ mod tests { let e = world.spawn(A).id(); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.deny_all(); - builder.without_required_components(|builder| { - builder.allow::(); - }); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .deny_all() + .without_required_components(|builder| { + builder.allow::(); + }) + .clone_entity(e, e_clone); assert_eq!(world.entity(e_clone).get::(), None); assert_eq!(world.entity(e_clone).get::(), Some(&B)); @@ -1102,7 +1202,7 @@ mod tests { #[test] fn clone_entity_with_dynamic_components() { const COMPONENT_SIZE: usize = 10; - fn test_handler(_world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { + fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) { // SAFETY: this handler is only going to be used with a component represented by [u8; COMPONENT_SIZE] unsafe { ctx.write_target_component_ptr(move |source_ptr, target_ptr| { @@ -1129,16 +1229,11 @@ mod tests { layout, None, true, + ComponentCloneBehavior::Custom(test_handler), ) }; let component_id = world.register_component_with_descriptor(descriptor); - let handlers = world.get_component_clone_handlers_mut(); - handlers.set_component_handler( - component_id, - ComponentCloneHandler::custom_handler(test_handler), - ); - let mut entity = world.spawn_empty(); let data = [5u8; COMPONENT_SIZE]; @@ -1151,8 +1246,7 @@ mod tests { let entity = entity.id(); let entity_clone = world.spawn_empty().id(); - let builder = EntityCloneBuilder::new(&mut world); - builder.clone_entity(entity, entity_clone); + EntityCloner::build(&mut world).clone_entity(entity, entity_clone); let ptr = world.get_by_id(entity, component_id).unwrap(); let clone_ptr = world.get_by_id(entity_clone, component_id).unwrap(); @@ -1164,4 +1258,71 @@ mod tests { ); } } + + #[test] + fn recursive_clone() { + let mut world = World::new(); + let root = world.spawn_empty().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) + .recursive(true) + .clone_entity(root, clone_root); + + let root_children = world + .entity(clone_root) + .get::() + .unwrap() + .iter() + .cloned() + .collect::>(); + + assert!(root_children.iter().all(|e| *e != child1 && *e != child2)); + assert_eq!(root_children.len(), 2); + let child1_children = world.entity(root_children[0]).get::().unwrap(); + assert_eq!(child1_children.len(), 1); + assert_ne!(child1_children[0], grandchild); + assert!(world.entity(root_children[1]).get::().is_none()); + + assert_eq!( + world.entity(root).get::().unwrap().deref(), + &[child1, child2] + ); + } + + #[test] + fn clone_with_reflect_from_world() { + #[derive(Component, Reflect, PartialEq, Eq, Debug)] + #[reflect(Component, FromWorld, from_reflect = false)] + struct SomeRef(#[entities] Entity); + + #[derive(Resource)] + struct FromWorldCalled(bool); + + impl FromWorld for SomeRef { + fn from_world(world: &mut World) -> Self { + world.insert_resource(FromWorldCalled(true)); + SomeRef(Entity::PLACEHOLDER) + } + } + let mut world = World::new(); + let registry = AppTypeRegistry::default(); + registry.write().register::(); + world.insert_resource(registry); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn(SomeRef(a)).id(); + let d = world.spawn_empty().id(); + let mut map = EntityHashMap::::new(); + map.insert(a, b); + map.insert(c, d); + + let cloned = EntityCloner::default().clone_entity_mapped(&mut world, c, &mut map); + assert_eq!(*world.entity(cloned).get::().unwrap(), SomeRef(b)); + assert!(world.resource::().0); + } } diff --git a/crates/bevy_ecs/src/entity/entity_set.rs b/crates/bevy_ecs/src/entity/entity_set.rs index 6b69897806071..9e1a92d8f1bb0 100644 --- a/crates/bevy_ecs/src/entity/entity_set.rs +++ b/crates/bevy_ecs/src/entity/entity_set.rs @@ -13,7 +13,7 @@ use core::{ option, result, }; -use super::Entity; +use super::{Entity, UniqueEntitySlice}; use bevy_platform_support::sync::Arc; @@ -310,7 +310,7 @@ unsafe impl EntitySetIterator for iter::StepBy {} /// Conversion from an `EntitySetIterator`. /// /// Some collections, while they can be constructed from plain iterators, -/// benefit strongly from the additional uniqeness guarantee [`EntitySetIterator`] offers. +/// benefit strongly from the additional uniqueness guarantee [`EntitySetIterator`] offers. /// Mirroring [`Iterator::collect`]/[`FromIterator::from_iter`], [`EntitySetIterator::collect_set`] and /// `FromEntitySetIterator::from_entity_set_iter` can be used for construction. /// @@ -350,6 +350,7 @@ impl UniqueEntityIter { Self { iter } } } + impl> UniqueEntityIter { /// Constructs a [`UniqueEntityIter`] from an iterator unsafely. /// @@ -359,6 +360,26 @@ impl> UniqueEntityIter { pub unsafe fn from_iterator_unchecked(iter: I) -> Self { Self { iter } } + + /// Returns the inner `I`. + pub fn into_inner(self) -> I { + self.iter + } + + /// Returns a reference to the inner `I`. + pub fn as_inner(&self) -> &I { + &self.iter + } + + /// Returns a mutable reference to the inner `I`. + /// + /// # Safety + /// + /// `self` must always contain an iterator that yields unique elements, + /// even while this reference is live. + pub unsafe fn as_mut_inner(&mut self) -> &mut I { + &mut self.iter + } } impl> Iterator for UniqueEntityIter { @@ -394,6 +415,24 @@ impl + AsRef<[T]>> AsRef<[T]> for Uniq } } +impl + AsRef<[T]>> + AsRef> for UniqueEntityIter +{ + fn as_ref(&self) -> &UniqueEntitySlice { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(self.iter.as_ref()) } + } +} + +impl + AsMut<[T]>> + AsMut> for UniqueEntityIter +{ + fn as_mut(&mut self) -> &mut UniqueEntitySlice { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.iter.as_mut()) } + } +} + // Default does not guarantee uniqueness, meaning `I` needs to be EntitySetIterator. impl Default for UniqueEntityIter { fn default() -> Self { @@ -431,7 +470,6 @@ mod tests { use crate::query::{QueryState, With}; use crate::system::Query; use crate::world::Mut; - use crate::{self as bevy_ecs}; use super::UniqueEntityIter; diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index 05c11781de788..caa02eaeac2bd 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -39,8 +39,8 @@ use super::{hash_map::EntityHashMap, VisitEntitiesMut}; /// /// impl MapEntities for Spring { /// fn map_entities(&mut self, entity_mapper: &mut M) { -/// self.a = entity_mapper.map_entity(self.a); -/// self.b = entity_mapper.map_entity(self.b); +/// self.a = entity_mapper.get_mapped(self.a); +/// self.b = entity_mapper.get_mapped(self.b); /// } /// } /// ``` @@ -55,7 +55,7 @@ pub trait MapEntities { impl MapEntities for T { fn map_entities(&mut self, entity_mapper: &mut M) { self.visit_entities_mut(|entity| { - *entity = entity_mapper.map_entity(*entity); + *entity = entity_mapper.get_mapped(*entity); }); } } @@ -67,6 +67,9 @@ 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. +/// /// ## Example /// /// ``` @@ -80,26 +83,61 @@ impl MapEntities for T { /// // Example implementation of EntityMapper where we map an entity to another entity if it exists /// // in the underlying `EntityHashMap`, otherwise we just return the original entity. /// impl EntityMapper for SimpleEntityMapper { -/// fn map_entity(&mut self, entity: Entity) -> Entity { +/// 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); +/// } /// } /// ``` pub trait EntityMapper { - /// Map an entity to another entity - fn map_entity(&mut self, entity: Entity) -> Entity; + /// Returns the "target" entity that maps to the given `source`. + fn get_mapped(&mut self, source: Entity) -> Entity; + + /// Maps the `target` entity to the given `source`. For some implementations this might not actually determine the result + /// of [`EntityMapper::get_mapped`]. + fn set_mapped(&mut self, source: Entity, target: Entity); +} + +impl EntityMapper for () { + #[inline] + fn get_mapped(&mut self, source: Entity) -> Entity { + source + } + + #[inline] + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} +} + +impl EntityMapper for (Entity, Entity) { + #[inline] + fn get_mapped(&mut self, source: Entity) -> Entity { + if source == self.0 { + self.1 + } else { + source + } + } + + fn set_mapped(&mut self, _source: Entity, _target: Entity) {} } impl EntityMapper for &mut dyn EntityMapper { - fn map_entity(&mut self, entity: Entity) -> Entity { - (*self).map_entity(entity) + fn get_mapped(&mut self, source: Entity) -> Entity { + (*self).get_mapped(source) + } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + (*self).set_mapped(source, target); } } impl EntityMapper for SceneEntityMapper<'_> { /// Returns the corresponding mapped entity or reserves a new dead entity ID in the current world if it is absent. - fn map_entity(&mut self, entity: Entity) -> Entity { - if let Some(&mapped) = self.map.get(&entity) { + fn get_mapped(&mut self, source: Entity) -> Entity { + if let Some(&mapped) = self.map.get(&source) { return mapped; } @@ -112,10 +150,25 @@ impl EntityMapper for SceneEntityMapper<'_> { // Prevent generations counter from being a greater value than HIGH_MASK. self.generations = (self.generations + 1) & HIGH_MASK; - self.map.insert(entity, new); + self.map.insert(source, new); new } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + self.map.insert(source, target); + } +} + +impl EntityMapper for EntityHashMap { + /// Returns the corresponding mapped entity or returns `entity` if there is no mapped entity + fn get_mapped(&mut self, source: Entity) -> Entity { + self.get(&source).cloned().unwrap_or(source) + } + + fn set_mapped(&mut self, source: Entity, target: Entity) { + self.insert(source, target); + } } /// A wrapper for [`EntityHashMap`], augmenting it with the ability to allocate new [`Entity`] references in a destination @@ -208,15 +261,15 @@ mod tests { let mut mapper = SceneEntityMapper::new(&mut map, &mut world); let mapped_ent = Entity::from_raw(FIRST_IDX); - let dead_ref = mapper.map_entity(mapped_ent); + let dead_ref = mapper.get_mapped(mapped_ent); assert_eq!( dead_ref, - mapper.map_entity(mapped_ent), + mapper.get_mapped(mapped_ent), "should persist the allocated mapping from the previous line" ); assert_eq!( - mapper.map_entity(Entity::from_raw(SECOND_IDX)).index(), + mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(), dead_ref.index(), "should re-use the same index for further dead refs" ); @@ -234,7 +287,7 @@ mod tests { let mut world = World::new(); let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| { - mapper.map_entity(Entity::from_raw(0)) + mapper.get_mapped(Entity::from_raw(0)) }); // Next allocated entity should be a further generation on the same index @@ -253,7 +306,7 @@ mod tests { // Create and exercise a SceneEntityMapper - should not panic because it flushes // `Entities` first. SceneEntityMapper::world_scope(&mut Default::default(), &mut world, |_, m| { - m.map_entity(Entity::PLACEHOLDER); + m.get_mapped(Entity::PLACEHOLDER); }); // The SceneEntityMapper should leave `Entities` in a flushed state. diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 8be4df3643149..853cb7e4818dc 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -66,8 +66,13 @@ mod index_set; pub use index_map::EntityIndexMap; pub use index_set::EntityIndexSet; +mod unique_slice; + +pub use unique_slice::*; + use crate::{ archetype::{ArchetypeId, ArchetypeRow}, + change_detection::MaybeLocation, identifier::{ error::IdentifierError, kinds::IdKind, @@ -78,12 +83,9 @@ use crate::{ }; use alloc::vec::Vec; use bevy_platform_support::sync::atomic::Ordering; -use core::{fmt, hash::Hash, mem, num::NonZero}; +use core::{fmt, hash::Hash, mem, num::NonZero, panic::Location}; use log::warn; -#[cfg(feature = "track_location")] -use core::panic::Location; - #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; @@ -828,7 +830,7 @@ impl Entities { } /// Returns the location of an [`Entity`]. - /// Note: for pending entities, returns `Some(EntityLocation::INVALID)`. + /// Note: for pending entities, returns `None`. #[inline] pub fn get(&self, entity: Entity) -> Option { if let Some(meta) = self.meta.get(entity.index() as usize) { @@ -980,42 +982,63 @@ impl Entities { /// Sets the source code location from which this entity has last been spawned /// or despawned. - #[cfg(feature = "track_location")] #[inline] - pub(crate) fn set_spawned_or_despawned_by(&mut self, index: u32, caller: &'static Location) { - let meta = self - .meta - .get_mut(index as usize) - .expect("Entity index invalid"); - meta.spawned_or_despawned_by = Some(caller); + 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 /// or if this entity has never existed. - #[cfg(feature = "track_location")] pub fn entity_get_spawned_or_despawned_by( &self, entity: Entity, - ) -> Option<&'static Location<'static>> { - self.meta - .get(entity.index() as usize) - .filter(|meta| + ) -> 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))) - .and_then(|meta| meta.spawned_or_despawned_by) + .map(|meta| meta.spawned_or_despawned_by) + }) + .map(Option::flatten) } - /// Constructs a message explaining why an entity does not exists, if known. + /// Constructs a message explaining why an entity does not exist, if known. pub(crate) fn entity_does_not_exist_error_details( &self, - _entity: Entity, + entity: Entity, ) -> EntityDoesNotExistDetails { EntityDoesNotExistDetails { - #[cfg(feature = "track_location")] - location: self.entity_get_spawned_or_despawned_by(_entity), + location: self.entity_get_spawned_or_despawned_by(entity), + } + } +} + +/// An error that occurs when a specified [`Entity`] does not exist. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +#[error("The entity with ID {entity} {details}")] +pub struct EntityDoesNotExistError { + /// The entity's ID. + pub entity: Entity, + /// Details on why the entity does not exist, if available. + pub details: EntityDoesNotExistDetails, +} + +impl EntityDoesNotExistError { + pub(crate) fn new(entity: Entity, entities: &Entities) -> Self { + Self { + entity, + details: entities.entity_does_not_exist_error_details(entity), } } } @@ -1024,26 +1047,22 @@ impl Entities { /// regarding an entity that did not exist. #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct EntityDoesNotExistDetails { - #[cfg(feature = "track_location")] - location: Option<&'static Location<'static>>, + location: MaybeLocation>>, } impl fmt::Display for EntityDoesNotExistDetails { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "track_location")] - if let Some(location) = self.location { - write!(f, "was despawned by {location}") - } else { - write!( + match self.location.into_option() { + Some(Some(location)) => write!(f, "was despawned by {location}"), + Some(None) => write!( f, "does not exist (index has been reused or was never spawned)" - ) + ), + None => write!( + f, + "does not exist (enable `track_location` feature for more details)" + ), } - #[cfg(not(feature = "track_location"))] - write!( - f, - "does not exist (enable `track_location` feature for more details)" - ) } } @@ -1054,8 +1073,7 @@ struct EntityMeta { /// The current location of the [`Entity`] pub location: EntityLocation, /// Location of the last spawn or despawn of this entity - #[cfg(feature = "track_location")] - spawned_or_despawned_by: Option<&'static Location<'static>>, + spawned_or_despawned_by: MaybeLocation>>, } impl EntityMeta { @@ -1063,8 +1081,7 @@ impl EntityMeta { const EMPTY: EntityMeta = EntityMeta { generation: NonZero::::MIN, location: EntityLocation::INVALID, - #[cfg(feature = "track_location")] - spawned_or_despawned_by: None, + spawned_or_despawned_by: MaybeLocation::new(None), }; } diff --git a/crates/bevy_ecs/src/entity/unique_slice.rs b/crates/bevy_ecs/src/entity/unique_slice.rs new file mode 100644 index 0000000000000..f3a6f66de7e87 --- /dev/null +++ b/crates/bevy_ecs/src/entity/unique_slice.rs @@ -0,0 +1,1668 @@ +use core::{ + borrow::Borrow, + cmp::Ordering, + fmt::Debug, + iter::FusedIterator, + ops::{ + Bound, Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, + RangeToInclusive, + }, + ptr, + slice::{self, SliceIndex}, +}; + +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::VecDeque, + rc::Rc, + sync::Arc, + vec::Vec, +}; + +use super::{ + unique_vec, EntitySet, EntitySetIterator, FromEntitySetIterator, TrustedEntityBorrow, + UniqueEntityIter, UniqueEntityVec, +}; + +/// A slice that contains only unique entities. +/// +/// It can be obtained by slicing [`UniqueEntityVec`]. +#[repr(transparent)] +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct UniqueEntitySlice([T]); + +impl UniqueEntitySlice { + /// Constructs a `UniqueEntitySlice` 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]. + unsafe { &*(ptr::from_ref(slice) as *const Self) } + } + + /// Constructs a `UniqueEntitySlice` 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]. + unsafe { &mut *(ptr::from_mut(slice) as *mut Self) } + } + + /// Casts to `self` to a standard slice. + pub const fn as_inner(&self) -> &[T] { + &self.0 + } + + /// Constructs a `UniqueEntitySlice` 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]. + 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]. + unsafe { Box::from_raw(Box::into_raw(self) as *mut [T]) } + } + + /// Constructs a `UniqueEntitySlice` 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]. + unsafe { Arc::from_raw(Arc::into_raw(slice) as *mut Self) } + } + + /// Casts `self` to the inner slice. + pub fn into_arc_inner(self: Arc) -> Arc<[T]> { + // SAFETY: UniqueEntitySlice is a transparent wrapper around [T]. + unsafe { Arc::from_raw(Arc::into_raw(self) as *mut [T]) } + } + + // Constructs a `UniqueEntitySlice` 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]. + 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]. + unsafe { Rc::from_raw(Rc::into_raw(self) as *mut [T]) } + } + + /// Returns the first and all the rest of the elements of the slice, or `None` if it is empty. + /// + /// Equivalent to [`[T]::split_first`](slice::split_first). + pub const fn split_first(&self) -> Option<(&T, &Self)> { + let Some((first, rest)) = self.0.split_first() else { + return None; + }; + // SAFETY: All elements in the original slice are unique. + Some((first, unsafe { Self::from_slice_unchecked(rest) })) + } + + /// Returns the last and all the rest of the elements of the slice, or `None` if it is empty. + /// + /// Equivalent to [`[T]::split_last`](slice::split_last). + pub const fn split_last(&self) -> Option<(&T, &Self)> { + let Some((last, rest)) = self.0.split_last() else { + return None; + }; + // SAFETY: All elements in the original slice are unique. + Some((last, unsafe { Self::from_slice_unchecked(rest) })) + } + + /// Returns a reference to a subslice. + /// + /// Equivalent to the range functionality of [`[T]::get`]. + /// + /// Note that only the inner [`[T]::get`] supports indexing with a [`usize`]. + /// + /// [`[T]::get`]: `slice::get` + pub fn get(&self, index: I) -> Option<&Self> + where + Self: Index, + I: SliceIndex<[T], Output = [T]>, + { + self.0.get(index).map(|slice| + // SAFETY: All elements in the original slice are unique. + unsafe { Self::from_slice_unchecked(slice) }) + } + + /// Returns a mutable reference to a subslice. + /// + /// Equivalent to the range functionality of [`[T]::get_mut`]. + /// + /// Note that `UniqueEntitySlice::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> + where + Self: Index, + I: SliceIndex<[T], Output = [T]>, + { + self.0.get_mut(index).map(|slice| + // SAFETY: All elements in the original slice are unique. + unsafe { Self::from_slice_unchecked_mut(slice) }) + } + + /// Returns a reference to a subslice, without doing bounds checking. + /// + /// Equivalent to the range functionality of [`[T]::get_unchecked`]. + /// + /// Note that only the inner [`[T]::get_unchecked`] supports indexing with a [`usize`]. + /// + /// # Safety + /// + /// `index` must be safe to use with [`[T]::get_unchecked`] + /// + /// [`[T]::get_unchecked`]: `slice::get_unchecked` + pub unsafe fn get_unchecked(&self, index: I) -> &Self + where + Self: Index, + I: SliceIndex<[T], Output = [T]>, + { + // SAFETY: All elements in the original slice are unique. + unsafe { Self::from_slice_unchecked(self.0.get_unchecked(index)) } + } + /// Returns a mutable reference to a subslice, without doing bounds checking. + /// + /// Equivalent to the range functionality of [`[T]::get_unchecked_mut`]. + /// + /// Note that `UniqueEntitySlice::get_unchecked_mut` cannot be called with an index. + /// + /// # Safety + /// + /// `index` must be safe to use with [`[T]::get_unchecked_mut`] + /// + /// [`[T]::get_unchecked_mut`]: `slice::get_unchecked_mut` + pub unsafe fn get_unchecked_mut(&mut self, index: I) -> &mut Self + where + Self: Index, + I: SliceIndex<[T], Output = [T]>, + { + // SAFETY: All elements in the original slice are unique. + unsafe { Self::from_slice_unchecked_mut(self.0.get_unchecked_mut(index)) } + } + + /// Returns an unsafe mutable pointer to the slice's buffer. + pub const fn as_mut_ptr(&mut self) -> *mut T { + self.0.as_mut_ptr() + } + + /// Returns the two unsafe mutable pointers spanning the slice. + pub const fn as_mut_ptr_range(&mut self) -> Range<*mut T> { + self.0.as_mut_ptr_range() + } + + /// Swaps two elements in the slice. + pub fn swap(&mut self, a: usize, b: usize) { + self.0.swap(a, b); + } + + /// Reverses the order of elements in the slice, in place. + pub fn reverse(&mut self) { + self.0.reverse(); + } + + /// Returns an iterator over the slice. + pub fn iter(&self) -> Iter<'_, T> { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityIter::from_iterator_unchecked(self.0.iter()) } + } + + /// Returns an iterator over all contiguous windows of length + /// `size`. + /// + /// Equivalent to [`[T]::windows`]. + /// + /// [`[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)) } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the + /// beginning of the slice. + /// + /// Equivalent to [`[T]::chunks`]. + /// + /// [`[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)) } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the + /// beginning of the slice. + /// + /// Equivalent to [`[T]::chunks_mut`]. + /// + /// [`[T]::chunks_mut`]: `slice::chunks_mut` + 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( + self.0.chunks_mut(chunk_size), + ) + } + } + + /// + /// + /// Equivalent to [`[T]::chunks_exact`]. + /// + /// [`[T]::chunks_exact`]: `slice::chunks_exact` + 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)) + } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the + /// beginning of the slice. + /// + /// Equivalent to [`[T]::chunks_exact_mut`]. + /// + /// [`[T]::chunks_exact_mut`]: `slice::chunks_exact_mut` + 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( + self.0.chunks_exact_mut(chunk_size), + ) + } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the end + /// of the slice. + /// + /// Equivalent to [`[T]::rchunks`]. + /// + /// [`[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)) } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the end + /// of the slice. + /// + /// Equivalent to [`[T]::rchunks_mut`]. + /// + /// [`[T]::rchunks_mut`]: `slice::rchunks_mut` + 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( + self.0.rchunks_mut(chunk_size), + ) + } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the + /// end of the slice. + /// + /// Equivalent to [`[T]::rchunks_exact`]. + /// + /// [`[T]::rchunks_exact`]: `slice::rchunks_exact` + 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)) + } + } + + /// Returns an iterator over `chunk_size` elements of the slice at a time, starting at the end + /// of the slice. + /// + /// Equivalent to [`[T]::rchunks_exact_mut`]. + /// + /// [`[T]::rchunks_exact_mut`]: `slice::rchunks_exact_mut` + 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( + self.0.rchunks_exact_mut(chunk_size), + ) + } + } + + /// Returns an iterator over the slice producing non-overlapping runs + /// of elements using the predicate to separate them. + /// + /// Equivalent to [`[T]::chunk_by`]. + /// + /// [`[T]::chunk_by`]: `slice::chunk_by` + pub fn chunk_by(&self, pred: F) -> ChunkBy<'_, T, F> + where + 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)) } + } + + /// Returns an iterator over the slice producing non-overlapping mutable + /// runs of elements using the predicate to separate them. + /// + /// Equivalent to [`[T]::chunk_by_mut`]. + /// + /// [`[T]::chunk_by_mut`]: `slice::chunk_by_mut` + pub fn chunk_by_mut(&mut self, pred: F) -> ChunkByMut<'_, T, F> + where + F: FnMut(&T, &T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.chunk_by_mut(pred)) + } + } + + /// Divides one slice into two at an index. + /// + /// Equivalent to [`[T]::split_at`](slice::split_at). + pub const fn split_at(&self, mid: usize) -> (&Self, &Self) { + let (left, right) = self.0.split_at(mid); + // SAFETY: All elements in the original slice are unique. + unsafe { + ( + Self::from_slice_unchecked(left), + Self::from_slice_unchecked(right), + ) + } + } + + /// Divides one mutable slice into two at an index. + /// + /// Equivalent to [`[T]::split_at_mut`](slice::split_at_mut). + pub const fn split_at_mut(&mut self, mid: usize) -> (&mut Self, &mut Self) { + let (left, right) = self.0.split_at_mut(mid); + // SAFETY: All elements in the original slice are unique. + unsafe { + ( + Self::from_slice_unchecked_mut(left), + Self::from_slice_unchecked_mut(right), + ) + } + } + + /// Divides one slice into two at an index, without doing bounds checking. + /// + /// Equivalent to [`[T]::split_at_unchecked`](slice::split_at_unchecked). + /// + /// # Safety + /// + /// `mid` must be safe to use in [`[T]::split_at_unchecked`]. + /// + /// [`[T]::split_at_unchecked`]: `slice::split_at_unchecked` + pub const unsafe fn split_at_unchecked(&self, mid: usize) -> (&Self, &Self) { + // SAFETY: The safety contract is upheld by the caller. + let (left, right) = unsafe { self.0.split_at_unchecked(mid) }; + // SAFETY: All elements in the original slice are unique. + unsafe { + ( + Self::from_slice_unchecked(left), + Self::from_slice_unchecked(right), + ) + } + } + + /// Divides one mutable slice into two at an index, without doing bounds checking. + /// + /// Equivalent to [`[T]::split_at_mut_unchecked`](slice::split_at_mut_unchecked). + /// + /// # Safety + /// + /// `mid` must be safe to use in [`[T]::split_at_mut_unchecked`]. + /// + /// [`[T]::split_at_mut_unchecked`]: `slice::split_at_mut_unchecked` + pub const unsafe fn split_at_mut_unchecked(&mut self, mid: usize) -> (&mut Self, &mut Self) { + // SAFETY: The safety contract is upheld by the caller. + let (left, right) = unsafe { self.0.split_at_mut_unchecked(mid) }; + // SAFETY: All elements in the original slice are unique. + unsafe { + ( + Self::from_slice_unchecked_mut(left), + Self::from_slice_unchecked_mut(right), + ) + } + } + + /// Divides one slice into two at an index, returning `None` if the slice is + /// too short. + /// + /// Equivalent to [`[T]::split_at_checked`](slice::split_at_checked). + pub const fn split_at_checked(&self, mid: usize) -> Option<(&Self, &Self)> { + let Some((left, right)) = self.0.split_at_checked(mid) else { + return None; + }; + // SAFETY: All elements in the original slice are unique. + unsafe { + Some(( + Self::from_slice_unchecked(left), + Self::from_slice_unchecked(right), + )) + } + } + + /// Divides one mutable slice into two at an index, returning `None` if the + /// slice is too short. + /// + /// Equivalent to [`[T]::split_at_mut_checked`](slice::split_at_mut_checked). + pub const fn split_at_mut_checked(&mut self, mid: usize) -> Option<(&mut Self, &mut Self)> { + let Some((left, right)) = self.0.split_at_mut_checked(mid) else { + return None; + }; + // SAFETY: All elements in the original slice are unique. + unsafe { + Some(( + Self::from_slice_unchecked_mut(left), + Self::from_slice_unchecked_mut(right), + )) + } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred`. + /// + /// Equivalent to [`[T]::split`]. + /// + /// [`[T]::split`]: `slice::split` + pub fn split(&self, pred: F) -> Split<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.split(pred)) } + } + + /// Returns an iterator over mutable subslices separated by elements that + /// match `pred`. + /// + /// Equivalent to [`[T]::split_mut`]. + /// + /// [`[T]::split_mut`]: `slice::split_mut` + pub fn split_mut(&mut self, pred: F) -> SplitMut<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.split_mut(pred)) + } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred`. + /// + /// Equivalent to [`[T]::split_inclusive`]. + /// + /// [`[T]::split_inclusive`]: `slice::split_inclusive` + pub fn split_inclusive(&self, pred: F) -> SplitInclusive<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.split_inclusive(pred)) + } + } + + /// Returns an iterator over mutable subslices separated by elements that + /// match `pred`. + /// + /// Equivalent to [`[T]::split_inclusive_mut`]. + /// + /// [`[T]::split_inclusive_mut`]: `slice::split_inclusive_mut` + pub fn split_inclusive_mut(&mut self, pred: F) -> SplitInclusiveMut<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked( + self.0.split_inclusive_mut(pred), + ) + } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred`, starting at the end of the slice and working backwards. + /// + /// Equivalent to [`[T]::rsplit`]. + /// + /// [`[T]::rsplit`]: `slice::rsplit` + pub fn rsplit(&self, pred: F) -> RSplit<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { UniqueEntitySliceIter::from_slice_iterator_unchecked(self.0.rsplit(pred)) } + } + + /// Returns an iterator over mutable subslices separated by elements that + /// match `pred`, starting at the end of the slice and working + /// backwards. + /// + /// Equivalent to [`[T]::rsplit_mut`]. + /// + /// [`[T]::rsplit_mut`]: `slice::rsplit_mut` + pub fn rsplit_mut(&mut self, pred: F) -> RSplitMut<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.rsplit_mut(pred)) + } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred`, limited to returning at most `n` items. + /// + /// Equivalent to [`[T]::splitn`]. + /// + /// [`[T]::splitn`]: `slice::splitn` + pub fn splitn(&self, n: usize, pred: F) -> SplitN<'_, T, F> + where + 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)) } + } + + /// Returns an iterator over mutable subslices separated by elements that match + /// `pred`, limited to returning at most `n` items. + /// + /// Equivalent to [`[T]::splitn_mut`]. + /// + /// [`[T]::splitn_mut`]: `slice::splitn_mut` + pub fn splitn_mut(&mut self, n: usize, pred: F) -> SplitNMut<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.splitn_mut(n, pred)) + } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred` limited to returning at most `n` items. + /// + /// Equivalent to [`[T]::rsplitn`]. + /// + /// [`[T]::rsplitn`]: `slice::rsplitn` + pub fn rsplitn(&self, n: usize, pred: F) -> RSplitN<'_, T, F> + where + 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)) } + } + + /// Returns an iterator over subslices separated by elements that match + /// `pred` limited to returning at most `n` items. + /// + /// Equivalent to [`[T]::rsplitn_mut`]. + /// + /// [`[T]::rsplitn_mut`]: `slice::rsplitn_mut` + pub fn rsplitn_mut(&mut self, n: usize, pred: F) -> RSplitNMut<'_, T, F> + where + F: FnMut(&T) -> bool, + { + // SAFETY: Any subslice of a unique slice is also unique. + unsafe { + UniqueEntitySliceIterMut::from_mut_slice_iterator_unchecked(self.0.rsplitn_mut(n, pred)) + } + } + + /// Sorts the slice **without** preserving the initial order of equal elements. + /// + /// Equivalent to [`[T]::sort_unstable`](slice::sort_unstable). + pub fn sort_unstable(&mut self) + where + T: Ord, + { + self.0.sort_unstable(); + } + + /// Sorts the slice with a comparison function, **without** preserving the initial order of + /// equal elements. + /// + /// Equivalent to [`[T]::sort_unstable_by`](slice::sort_unstable_by). + pub fn sort_unstable_by(&mut self, compare: F) + where + F: FnMut(&T, &T) -> Ordering, + { + self.0.sort_unstable_by(compare); + } + + /// Sorts the slice with a key extraction function, **without** preserving the initial order of + /// equal elements. + /// + /// Equivalent to [`[T]::sort_unstable_by_key`](slice::sort_unstable_by_key). + pub fn sort_unstable_by_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: Ord, + { + self.0.sort_unstable_by_key(f); + } + + /// Rotates the slice in-place such that the first `mid` elements of the + /// slice move to the end while the last `self.len() - mid` elements move to + /// the front. + /// + /// Equivalent to [`[T]::rotate_left`](slice::rotate_left). + pub fn rotate_left(&mut self, mid: usize) { + self.0.rotate_left(mid); + } + + /// Rotates the slice in-place such that the first `self.len() - k` + /// elements of the slice move to the end while the last `k` elements move + /// to the front. + /// + /// Equivalent to [`[T]::rotate_right`](slice::rotate_right). + pub fn rotate_right(&mut self, mid: usize) { + self.0.rotate_right(mid); + } + + /// Sorts the slice, preserving initial order of equal elements. + /// + /// Equivalent to [`[T]::sort`](slice::sort()). + pub fn sort(&mut self) + where + T: Ord, + { + self.0.sort(); + } + + /// Sorts the slice with a comparison function, preserving initial order of equal elements. + /// + /// Equivalent to [`[T]::sort_by`](slice::sort_by). + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&T, &T) -> Ordering, + { + self.0.sort_by(compare); + } + + /// Sorts the slice with a key extraction function, preserving initial order of equal elements. + /// + /// Equivalent to [`[T]::sort_by_key`](slice::sort_by_key). + pub fn sort_by_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: Ord, + { + self.0.sort_by_key(f); + } + + // Sorts the slice with a key extraction function, preserving initial order of equal elements. + /// + /// Equivalent to [`[T]::sort_by_cached_key`](slice::sort_by_cached_key). + pub fn sort_by_cached_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: Ord, + { + self.0.sort_by_cached_key(f); + } + + /// Copies self into a new `UniqueEntityVec`. + pub fn to_vec(&self) -> UniqueEntityVec + where + T: Clone, + { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityVec::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 { + // 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) + } + } +} + +/// Converts a reference to T into a slice of length 1 (without copying). +pub const fn from_ref(s: &T) -> &UniqueEntitySlice { + // SAFETY: A slice with a length of 1 is always unique. + unsafe { UniqueEntitySlice::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 { + // SAFETY: A slice with a length of 1 is always unique. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice::from_mut(s)) } +} + +/// Forms a slice from a pointer and a length. +/// +/// Equivalent to [`slice::from_raw_parts`]. +/// +/// # Safety +/// +/// [`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>( + data: *const T, + len: usize, +) -> &'a UniqueEntitySlice { + // SAFETY: The safety contract is upheld by the caller. + unsafe { UniqueEntitySlice::from_slice_unchecked(slice::from_raw_parts(data, len)) } +} + +/// Performs the same functionality as [`from_raw_parts`], except that a mutable slice is returned. +/// +/// Equivalent to [`slice::from_raw_parts_mut`]. +/// +/// # Safety +/// +/// [`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>( + data: *mut T, + len: usize, +) -> &'a mut UniqueEntitySlice { + // SAFETY: The safety contract is upheld by the caller. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(slice::from_raw_parts_mut(data, len)) } +} + +/// Casts a slice of entity slices to a slice of [`UniqueEntitySlice`]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>( + slice: &'b [&'a [T]], +) -> &'b [&'a UniqueEntitySlice] { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { &*(ptr::from_ref(slice) as *const [&UniqueEntitySlice]) } +} + +/// Casts a mutable slice of entity slices to a slice of [`UniqueEntitySlice`]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>( + slice: &'b mut [&'a [T]], +) -> &'b mut [&'a UniqueEntitySlice] { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { &mut *(ptr::from_mut(slice) as *mut [&UniqueEntitySlice]) } +} + +/// Casts a mutable slice of mutable entity slices to a slice of mutable [`UniqueEntitySlice`]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>( + slice: &'b mut [&'a mut [T]], +) -> &'b mut [&'a mut UniqueEntitySlice] { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { &mut *(ptr::from_mut(slice) as *mut [&mut UniqueEntitySlice]) } +} + +impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a UniqueEntitySlice { + type Item = &'a T; + + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a Box> { + type Item = &'a T; + + type IntoIter = Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for Box> { + type Item = T; + + type IntoIter = unique_vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.into_vec().into_iter() + } +} + +impl Deref for UniqueEntitySlice { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[T]> for UniqueEntitySlice { + fn as_ref(&self) -> &[T] { + self + } +} + +impl AsRef for UniqueEntitySlice { + fn as_ref(&self) -> &Self { + self + } +} + +impl AsMut for UniqueEntitySlice { + fn as_mut(&mut self) -> &mut Self { + self + } +} + +impl Borrow<[T]> for UniqueEntitySlice { + fn borrow(&self) -> &[T] { + self + } +} + +impl Clone for Box> { + fn clone(&self) -> Self { + self.to_vec().into_boxed_slice() + } +} + +impl Default for &UniqueEntitySlice { + fn default() -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(Default::default()) } + } +} + +impl Default for &mut UniqueEntitySlice { + fn default() -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(Default::default()) } + } +} + +impl Default for Box> { + fn default() -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::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> +{ + fn from(value: &'a UniqueEntitySlice) -> Self { + Cow::Borrowed(value) + } +} + +impl<'a, T: TrustedEntityBorrow + Clone> From>> + for Box> +{ + fn from(value: Cow<'a, UniqueEntitySlice>) -> Self { + match value { + Cow::Borrowed(slice) => Box::from(slice), + Cow::Owned(slice) => Box::from(slice), + } + } +} + +impl From> for Box> { + fn from(value: UniqueEntityVec) -> Self { + value.into_boxed_slice() + } +} + +impl FromIterator for Box> { + fn from_iter>(iter: I) -> Self { + iter.into_iter() + .collect::>() + .into_boxed_slice() + } +} + +impl FromEntitySetIterator for Box> { + fn from_entity_set_iter>(iter: I) -> Self { + iter.into_iter() + .collect_set::>() + .into_boxed_slice() + } +} + +impl, U: TrustedEntityBorrow> PartialEq> + for &UniqueEntitySlice +{ + fn eq(&self, other: &UniqueEntityVec) -> bool { + self.0.eq(other.as_vec()) + } +} + +impl, U: TrustedEntityBorrow> PartialEq> + for &mut UniqueEntitySlice +{ + fn eq(&self, other: &UniqueEntityVec) -> bool { + self.0.eq(other.as_vec()) + } +} + +impl, U: TrustedEntityBorrow> PartialEq> + for UniqueEntitySlice +{ + fn eq(&self, other: &UniqueEntityVec) -> bool { + self.0.eq(other.as_vec()) + } +} + +impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&UniqueEntitySlice> + for [T; N] +{ + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl + Clone, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> + for Cow<'_, [T]> +{ + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.eq(&&other.0) + } +} + +impl + Clone, U: TrustedEntityBorrow> + PartialEq<&UniqueEntitySlice> for Cow<'_, UniqueEntitySlice> +{ + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.0.eq(&other.0) + } +} + +impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> for Vec { + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> for VecDeque { + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.eq(&&other.0) + } +} + +impl, U: TrustedEntityBorrow, const N: usize> PartialEq<&mut UniqueEntitySlice> + for [T; N] +{ + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl + Clone, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> + for Cow<'_, [T]> +{ + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.eq(&&**other) + } +} + +impl + Clone, U: TrustedEntityBorrow> + PartialEq<&mut UniqueEntitySlice> for Cow<'_, UniqueEntitySlice> +{ + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.0.eq(&other.0) + } +} + +impl + Clone, U: TrustedEntityBorrow> + PartialEq> for Cow<'_, UniqueEntitySlice> +{ + fn eq(&self, other: &UniqueEntityVec) -> bool { + self.0.eq(other.as_vec()) + } +} + +impl, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> for Vec { + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl, U: TrustedEntityBorrow> PartialEq<&mut UniqueEntitySlice> for VecDeque { + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.eq(&&other.0) + } +} + +impl, U: TrustedEntityBorrow> PartialEq> + for [T] +{ + fn eq(&self, other: &UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl, U: TrustedEntityBorrow, const N: usize> PartialEq> + for [T; N] +{ + fn eq(&self, other: &UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl, U: TrustedEntityBorrow> PartialEq> + for Vec +{ + fn eq(&self, other: &UniqueEntitySlice) -> bool { + self.eq(&other.0) + } +} + +impl, U, const N: usize> PartialEq<[U; N]> + for &UniqueEntitySlice +{ + fn eq(&self, other: &[U; N]) -> bool { + self.0.eq(other) + } +} + +impl, U, const N: usize> PartialEq<[U; N]> + for &mut UniqueEntitySlice +{ + fn eq(&self, other: &[U; N]) -> bool { + self.0.eq(other) + } +} + +impl, U, const N: usize> PartialEq<[U; N]> + for UniqueEntitySlice +{ + fn eq(&self, other: &[U; N]) -> bool { + self.0.eq(other) + } +} + +impl, U> PartialEq> for &UniqueEntitySlice { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(other) + } +} + +impl, U> PartialEq> for &mut UniqueEntitySlice { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(other) + } +} + +impl, U> PartialEq> for UniqueEntitySlice { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(other) + } +} + +impl ToOwned for UniqueEntitySlice { + type Owned = UniqueEntityVec; + + fn to_owned(&self) -> Self::Owned { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntityVec::from_vec_unchecked(self.0.to_owned()) } + } +} + +impl Index<(Bound, Bound)> for UniqueEntitySlice { + type Output = Self; + fn index(&self, key: (Bound, Bound)) -> &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 = Self; + fn index(&self, key: Range) -> &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 = Self; + fn index(&self, key: RangeFrom) -> &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 = Self; + fn index(&self, key: RangeFull) -> &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; + 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; + 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; + 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 { + type Output = T; + + fn index(&self, index: usize) -> &T { + &self.0[index] + } +} + +impl IndexMut<(Bound, Bound)> for UniqueEntitySlice { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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)) } + } +} + +/// Immutable slice iterator. +/// +/// This struct is created by [`iter`] method on [`UniqueEntitySlice`] and +/// the [`IntoIterator`] impls on it and [`UniqueEntityVec`]. +/// +/// [`iter`]: `UniqueEntitySlice::iter` +/// [`into_iter`]: UniqueEntitySlice::into_iter +pub type Iter<'a, T> = UniqueEntityIter>; + +impl<'a, T: TrustedEntityBorrow> 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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + } +} + +/// Mutable slice iterator. +pub type IterMut<'a, T> = UniqueEntityIter>; + +impl<'a, T: TrustedEntityBorrow> 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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + } +} + +/// An iterator that yields `&UniqueEntitySlice`. 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(crate) iter: I, +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> UniqueEntitySliceIter<'a, T, I> { + /// Constructs a [`UniqueEntitySliceIter`] from a slice iterator unsafely. + /// + /// # Safety + /// + /// All elements in each of the slices must be unique. + pub unsafe fn from_slice_iterator_unchecked(iter: I) -> Self { + Self { iter } + } + + /// Returns the inner `I`. + pub fn into_inner(self) -> I { + self.iter + } + + /// Returns a reference to the inner `I`. + pub fn as_inner(&self) -> &I { + &self.iter + } + + /// Returns a mutable reference to the inner `I`. + /// + /// # Safety + /// + /// `self` must always contain an iterator that yields unique elements, + /// even while this reference is live. + pub unsafe fn as_mut_inner(&mut self) -> &mut I { + &mut self.iter + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator + for UniqueEntitySliceIter<'a, T, I> +{ + type Item = &'a UniqueEntitySlice; + + 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) }) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: ExactSizeIterator> ExactSizeIterator + for UniqueEntitySliceIter<'a, T, I> +{ +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: DoubleEndedIterator> DoubleEndedIterator + for UniqueEntitySliceIter<'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) }) + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: FusedIterator> FusedIterator + for UniqueEntitySliceIter<'a, T, I> +{ +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsRef<[&'a [T]]>> + AsRef<[&'a UniqueEntitySlice]> for UniqueEntitySliceIter<'a, T, I> +{ + fn as_ref(&self) -> &[&'a UniqueEntitySlice] { + // SAFETY: + unsafe { cast_slice_of_unique_entity_slice(self.iter.as_ref()) } + } +} + +/// An iterator over overlapping subslices of length `size`. +/// +/// This struct is created by [`UniqueEntitySlice::windows`]. +pub type Windows<'a, T> = UniqueEntitySliceIter<'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> = UniqueEntitySliceIter<'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> = UniqueEntitySliceIter<'a, T, slice::ChunksExact<'a, T>>; + +impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIter<'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 { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { UniqueEntitySlice::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> = UniqueEntitySliceIter<'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> = UniqueEntitySliceIter<'a, T, slice::RChunksExact<'a, T>>; + +impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIter<'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 { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { UniqueEntitySlice::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, T, P> = UniqueEntitySliceIter<'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, T, P> = UniqueEntitySliceIter<'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`]. +pub type SplitInclusive<'a, T, P> = UniqueEntitySliceIter<'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, T, P> = UniqueEntitySliceIter<'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, T, P> = UniqueEntitySliceIter<'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, T, P> = UniqueEntitySliceIter<'a, T, slice::RSplitN<'a, T, P>>; + +/// An iterator that yields `&mut UniqueEntitySlice`. Note that an entity may appear +/// in multiple slices, depending on the wrapped iterator. +#[derive(Debug)] +pub struct UniqueEntitySliceIterMut< + 'a, + T: TrustedEntityBorrow + 'a, + I: Iterator, +> { + pub(crate) iter: I, +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> + UniqueEntitySliceIterMut<'a, T, I> +{ + /// Constructs a [`UniqueEntitySliceIterMut`] from a mutable slice iterator unsafely. + /// + /// # Safety + /// + /// All elements in each of the slices must be unique. + pub unsafe fn from_mut_slice_iterator_unchecked(iter: I) -> Self { + Self { iter } + } + + /// Returns the inner `I`. + pub fn into_inner(self) -> I { + self.iter + } + + /// Returns a reference to the inner `I`. + pub fn as_inner(&self) -> &I { + &self.iter + } + + /// Returns a mutable reference to the inner `I`. + /// + /// # Safety + /// + /// `self` must always contain an iterator that yields unique elements, + /// even while this reference is live. + pub unsafe fn as_mut_inner(&mut self) -> &mut I { + &mut self.iter + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator> Iterator + for UniqueEntitySliceIterMut<'a, T, I> +{ + type Item = &'a mut UniqueEntitySlice; + + 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) }) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: ExactSizeIterator> ExactSizeIterator + for UniqueEntitySliceIterMut<'a, T, I> +{ +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: DoubleEndedIterator> + DoubleEndedIterator for UniqueEntitySliceIterMut<'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) }) + } +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: FusedIterator> FusedIterator + for UniqueEntitySliceIterMut<'a, T, I> +{ +} + +impl<'a, T: TrustedEntityBorrow + 'a, I: Iterator + AsRef<[&'a [T]]>> + AsRef<[&'a UniqueEntitySlice]> for UniqueEntitySliceIterMut<'a, T, I> +{ + fn as_ref(&self) -> &[&'a UniqueEntitySlice] { + // 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> +{ + fn as_mut(&mut self) -> &mut [&'a mut UniqueEntitySlice] { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { cast_slice_of_mut_unique_entity_slice_mut(self.iter.as_mut()) } + } +} + +/// 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> = UniqueEntitySliceIterMut<'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`]. +pub type ChunksExactMut<'a, T> = UniqueEntitySliceIterMut<'a, T, slice::ChunksExactMut<'a, T>>; + +impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIterMut<'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 { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { UniqueEntitySlice::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> = UniqueEntitySliceIterMut<'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`]. +pub type RChunksExactMut<'a, T> = UniqueEntitySliceIterMut<'a, T, slice::RChunksExactMut<'a, T>>; + +impl<'a, T: TrustedEntityBorrow> UniqueEntitySliceIterMut<'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 { + // SAFETY: All elements in the original iterator are unique slices. + unsafe { UniqueEntitySlice::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`]. +pub type ChunkByMut<'a, T, P> = UniqueEntitySliceIterMut<'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, T, P> = UniqueEntitySliceIterMut<'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`]. +pub type SplitInclusiveMut<'a, T, P> = + UniqueEntitySliceIterMut<'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, T, P> = UniqueEntitySliceIterMut<'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, T, P> = UniqueEntitySliceIterMut<'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`]. +pub type RSplitNMut<'a, T, P> = UniqueEntitySliceIterMut<'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 32b4f0b03751f..9ea1f9c7a6b39 100644 --- a/crates/bevy_ecs/src/entity/unique_vec.rs +++ b/crates/bevy_ecs/src/entity/unique_vec.rs @@ -1,12 +1,14 @@ use core::{ - borrow::Borrow, + borrow::{Borrow, BorrowMut}, mem::MaybeUninit, - ops::{Index, RangeBounds}, - slice, + ops::{ + Bound, Deref, DerefMut, Index, IndexMut, Range, RangeBounds, RangeFrom, RangeFull, + RangeInclusive, RangeTo, RangeToInclusive, + }, }; use alloc::{ - borrow::Cow, + borrow::{Cow, ToOwned}, boxed::Box, collections::{BTreeSet, BinaryHeap, TryReserveError, VecDeque}, rc::Rc, @@ -14,7 +16,10 @@ use alloc::{ vec::{self, Vec}, }; -use super::{EntitySet, FromEntitySetIterator, TrustedEntityBorrow, UniqueEntityIter}; +use super::{ + unique_slice, EntitySet, FromEntitySetIterator, TrustedEntityBorrow, UniqueEntityIter, + UniqueEntitySlice, +}; /// A `Vec` that contains only unique entities. /// @@ -140,6 +145,22 @@ 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()) } + } + + /// Extracts a slice containing the entire vector. + pub fn as_slice(&self) -> &UniqueEntitySlice { + self + } + + /// Extracts a mutable slice of the entire vector. + pub fn as_mut_slice(&mut self) -> &mut UniqueEntitySlice { + self + } + /// Shortens the vector, keeping the first `len` elements and dropping /// the rest. /// @@ -343,6 +364,12 @@ 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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.0.leak()) } + } + /// Returns the remaining spare capacity of the vector as a slice of /// [`MaybeUninit`]. /// @@ -370,7 +397,7 @@ impl UniqueEntityVec { I: EntitySet, { // SAFETY: `self` and thus `range` contains only unique elements. - UniqueEntityIter::from_iterator_unchecked(self.0.splice(range, replace_with)) + unsafe { UniqueEntityIter::from_iterator_unchecked(self.0.splice(range, replace_with)) } } } @@ -380,13 +407,29 @@ impl Default for UniqueEntityVec { } } +impl Deref for UniqueEntityVec { + type Target = UniqueEntitySlice; + + fn deref(&self) -> &Self::Target { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(&self.0) } + } +} + +impl DerefMut for UniqueEntityVec { + 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) } + } +} + impl<'a, T: TrustedEntityBorrow> IntoIterator for &'a UniqueEntityVec where &'a T: TrustedEntityBorrow, { type Item = &'a T; - type IntoIter = UniqueEntityIter>; + type IntoIter = unique_slice::Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { // SAFETY: `self` contains only unique elements. @@ -411,6 +454,12 @@ impl AsMut for UniqueEntityVec { } } +impl AsMut> for UniqueEntityVec { + fn as_mut(&mut self) -> &mut UniqueEntitySlice { + self + } +} + impl AsRef for UniqueEntityVec { fn as_ref(&self) -> &Self { self @@ -435,12 +484,30 @@ impl AsRef<[T]> for UniqueEntityVec { } } +impl AsRef> for UniqueEntityVec { + fn as_ref(&self) -> &UniqueEntitySlice { + self + } +} + impl Borrow<[T]> for UniqueEntityVec { fn borrow(&self) -> &[T] { &self.0 } } +impl Borrow> for UniqueEntityVec { + fn borrow(&self) -> &UniqueEntitySlice { + self + } +} + +impl BorrowMut> for UniqueEntityVec { + fn borrow_mut(&mut self) -> &mut UniqueEntitySlice { + self + } +} + impl, U> PartialEq> for UniqueEntityVec { fn eq(&self, other: &Vec) -> bool { self.0.eq(other) @@ -453,12 +520,28 @@ impl, U> PartialEq<&[U]> for UniqueEntityV } } +impl, U: TrustedEntityBorrow> PartialEq<&UniqueEntitySlice> + for UniqueEntityVec +{ + fn eq(&self, other: &&UniqueEntitySlice) -> bool { + self.0.eq(other) + } +} + impl, U> PartialEq<&mut [U]> for UniqueEntityVec { fn eq(&self, other: &&mut [U]) -> bool { self.0.eq(other) } } +impl, U: TrustedEntityBorrow> + PartialEq<&mut UniqueEntitySlice> for UniqueEntityVec +{ + fn eq(&self, other: &&mut UniqueEntitySlice) -> bool { + self.0.eq(other) + } +} + impl, U, const N: usize> PartialEq<&[U; N]> for UniqueEntityVec { @@ -481,6 +564,14 @@ impl, U> PartialEq<[U]> for UniqueEntityVe } } +impl, U: TrustedEntityBorrow> PartialEq> + for UniqueEntityVec +{ + fn eq(&self, other: &UniqueEntitySlice) -> bool { + self.0.eq(&**other) + } +} + impl, U, const N: usize> PartialEq<[U; N]> for UniqueEntityVec { @@ -529,6 +620,33 @@ impl, U: TrustedEntityBorrow> PartialEq> for } } +impl From<&UniqueEntitySlice> for UniqueEntityVec { + fn from(value: &UniqueEntitySlice) -> Self { + value.to_vec() + } +} + +impl From<&mut UniqueEntitySlice> for UniqueEntityVec { + fn from(value: &mut UniqueEntitySlice) -> Self { + value.to_vec() + } +} + +impl From>> for UniqueEntityVec { + fn from(value: Box>) -> Self { + value.into_vec() + } +} + +impl From>> for UniqueEntityVec +where + UniqueEntitySlice: ToOwned>, +{ + fn from(value: Cow>) -> Self { + value.into_owned() + } +} + impl From<&[T; 1]> for UniqueEntityVec { fn from(value: &[T; 1]) -> Self { Self(Vec::from(value)) @@ -577,12 +695,27 @@ impl<'a, T: TrustedEntityBorrow + Clone> From> for Cow<'a, [T } } +impl<'a, T: TrustedEntityBorrow + Clone> From> + for Cow<'a, UniqueEntitySlice> +{ + fn from(value: UniqueEntityVec) -> Self { + Cow::Owned(value) + } +} + impl From> for Arc<[T]> { fn from(value: UniqueEntityVec) -> Self { Arc::from(value.0) } } +impl From> for Arc> { + fn from(value: UniqueEntityVec) -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_arc_slice_unchecked(Arc::from(value.0)) } + } +} + impl From> for BinaryHeap { fn from(value: UniqueEntityVec) -> Self { BinaryHeap::from(value.0) @@ -601,6 +734,13 @@ impl From> for Rc<[T]> { } } +impl From> for Rc> { + fn from(value: UniqueEntityVec) -> Self { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_rc_slice_unchecked(Rc::from(value.0)) } + } +} + impl From> for VecDeque { fn from(value: UniqueEntityVec) -> Self { VecDeque::from(value.0) @@ -709,6 +849,62 @@ impl<'a, T: TrustedEntityBorrow + Copy + 'a> Extend<&'a T> for UniqueEntityVec Index<(Bound, Bound)> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + +impl Index> for UniqueEntityVec { + type Output = UniqueEntitySlice; + 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)) } + } +} + impl Index for UniqueEntityVec { type Output = T; fn index(&self, key: usize) -> &T { @@ -716,20 +912,97 @@ impl Index for UniqueEntityVec { } } +impl IndexMut<(Bound, Bound)> for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut> for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut> for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut> for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut> for UniqueEntityVec { + 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)) } + } +} + +impl IndexMut> for UniqueEntityVec { + 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)) } + } +} + +/// An iterator that moves out of a vector. +/// +/// This `struct` is created by the [`IntoIterator::into_iter`] trait +/// method on [`UniqueEntityVec`]. +pub type IntoIter = 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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::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 { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked_mut(self.as_mut_inner().as_mut_slice()) } + } +} + /// A draining iterator for [`UniqueEntityVec`]. /// /// This struct is created by [`UniqueEntityVec::drain`]. /// See its documentation for more. pub type Drain<'a, T> = UniqueEntityIter>; +impl<'a, T: TrustedEntityBorrow> UniqueEntityIter> { + /// Returns the remaining items of this iterator as a slice. + /// + /// Equivalent to [`vec::Drain::as_slice`]. + pub fn as_slice(&self) -> &UniqueEntitySlice { + // SAFETY: All elements in the original slice are unique. + unsafe { UniqueEntitySlice::from_slice_unchecked(self.as_inner().as_slice()) } + } +} + /// A splicing iterator for [`UniqueEntityVec`]. /// /// This struct is created by [`UniqueEntityVec::splice`]. /// See its documentation for more. pub type Splice<'a, I> = UniqueEntityIter>; - -/// An iterator that moves out of a vector. -/// -/// This `struct` is created by the [`IntoIterator::into_iter`] trait -/// method on [`UniqueEntityVec`]. -pub type IntoIter = UniqueEntityIter>; diff --git a/crates/bevy_ecs/src/entity/visit_entities.rs b/crates/bevy_ecs/src/entity/visit_entities.rs index 477216a4bcfef..734c96e1132b9 100644 --- a/crates/bevy_ecs/src/entity/visit_entities.rs +++ b/crates/bevy_ecs/src/entity/visit_entities.rs @@ -57,7 +57,6 @@ impl VisitEntitiesMut for Entity { #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, entity::{hash_map::EntityHashMap, MapEntities, SceneEntityMapper}, world::World, }; diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index f96c78c1632c9..d4b1ad266c077 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -1,14 +1,55 @@ -//! Types for entity disabling. -//! //! Disabled entities do not show up in queries unless the query explicitly mentions them. //! -//! If for example we have `Disabled` as an entity disabling component, when you add `Disabled` -//! to an entity, the entity will only be visible to queries with a filter like -//! [`With`]`` or query data like [`Has`]``. +//! Entities which are disabled in this way are not removed from the [`World`], +//! and their relationships remain intact. +//! In many cases, you may want to disable entire trees of entities at once, +//! using [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive). +//! +//! While Bevy ships with a built-in [`Disabled`] component, you can also create your own +//! disabling components, which will operate in the same way but can have distinct semantics. +//! +//! ``` +//! use bevy_ecs::prelude::*; +//! +//! // Our custom disabling component! +//! #[derive(Component, Clone)] +//! struct Prefab; +//! +//! #[derive(Component)] +//! struct A; +//! +//! let mut world = World::new(); +//! world.register_disabling_component::(); +//! world.spawn((A, Prefab)); +//! world.spawn((A,)); +//! world.spawn((A,)); +//! +//! let mut normal_query = world.query::<&A>(); +//! assert_eq!(2, normal_query.iter(&world).count()); +//! +//! let mut prefab_query = world.query_filtered::<&A, With>(); +//! assert_eq!(1, prefab_query.iter(&world).count()); +//! +//! let mut maybe_prefab_query = world.query::<(&A, Has)>(); +//! assert_eq!(3, maybe_prefab_query.iter(&world).count()); +//! ``` +//! +//! ## Default query filters +//! +//! In Bevy, entity disabling is implemented through the construction of a global "default query filter". +//! Queries which do not explicitly mention the disabled component will not include entities with that component. +//! If an entity has multiple disabling components, it will only be included in queries that mention all of them. +//! +//! For example, `Query<&Position>` will not include entities with the [`Disabled`] component, +//! even if they have a `Position` component, +//! but `Query<&Position, With>` or `Query<(&Position, Has)>` will see them. +//! +//! Entities with disabling components are still present in the [`World`] and can be accessed directly, +//! using methods on [`World`] or [`Commands`](crate::prelude::Commands). //! -//! ### Note +//! ### Warnings //! -//! Currently only queries for which the cache is built after enabling a filter will have entities +//! Currently, only queries for which the cache is built after enabling a default query filter will have entities //! with those components filtered. As a result, they should generally only be modified before the //! app starts. //! @@ -16,53 +57,131 @@ //! the enire [`World`], especially when they cause queries to mix sparse and table components. //! See [`Query` performance] for more info. //! +//! Custom disabling components can cause significant interoperability issues within the ecosystem, +//! as users must be aware of each disabling component in use. +//! Libraries should think carefully about whether they need to use a new disabling component, +//! and clearly communicate their presence to their users to avoid the new for library compatibility flags. +//! //! [`With`]: crate::prelude::With //! [`Has`]: crate::prelude::Has //! [`World`]: crate::prelude::World //! [`Query` performance]: crate::prelude::Query#performance -use crate as bevy_ecs; use crate::{ component::{ComponentId, Components, StorageType}, query::FilteredAccess, + world::{FromWorld, World}, }; use bevy_ecs_macros::{Component, Resource}; +use smallvec::SmallVec; #[cfg(feature = "bevy_reflect")] use {crate::reflect::ReflectComponent, bevy_reflect::Reflect}; -/// A marker component for disabled entities. See [the module docs] for more info. +/// A marker component for disabled entities. +/// +/// Semantically, this component is used to mark entities that are temporarily disabled (typically for gameplay reasons), +/// but will likely be re-enabled at some point. +/// +/// Like all disabling components, this only disables the entity itself, +/// not its children or other entities that reference it. +/// To disable an entire tree of entities, use [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive). +/// +/// Every [`World`] has a default query filter that excludes entities with this component, +/// registered in the [`DefaultQueryFilters`] resource. +/// See [the module docs] for more info. /// /// [the module docs]: crate::entity_disabling -#[derive(Component)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))] +#[derive(Component, Clone, Debug)] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Component), + reflect(Debug) +)] +// This component is registered as a disabling component during World::bootstrap pub struct Disabled; -/// The default filters for all queries, these are used to globally exclude entities from queries. +/// Default query filters work by excluding entities with certain components from most queries. +/// +/// If a query does not explicitly mention a given disabling component, it will not include entities with that component. +/// To be more precise, this checks if the query's [`FilteredAccess`] contains the component, +/// and if it does not, adds a [`Without`](crate::prelude::Without) filter for that component to the query. +/// +/// This resource is initialized in the [`World`] whenever a new world is created, +/// with the [`Disabled`] component as a disabling component. +/// +/// Note that you can remove default query filters by overwriting the [`DefaultQueryFilters`] resource. +/// This can be useful as a last resort escape hatch, but is liable to break compatibility with other libraries. +/// /// See the [module docs](crate::entity_disabling) for more info. -#[derive(Resource, Default, Debug)] +/// +/// +/// # Warning +/// +/// Default query filters are a global setting that affects all queries in the [`World`], +/// and incur a small performance cost for each query. +/// +/// They can cause significant interoperability issues within the ecosystem, +/// as users must be aware of each disabling component in use. +/// +/// Think carefully about whether you need to use a new disabling component, +/// and clearly communicate their presence in any libraries you publish. +#[derive(Resource, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] pub struct DefaultQueryFilters { - disabled: Option, + // We only expect a few components per application to act as disabling components, so we use a SmallVec here + // to avoid heap allocation in most cases. + disabling: SmallVec<[ComponentId; 4]>, +} + +impl FromWorld for DefaultQueryFilters { + fn from_world(world: &mut World) -> Self { + let mut filters = DefaultQueryFilters::empty(); + let disabled_component_id = world.register_component::(); + filters.register_disabling_component(disabled_component_id); + filters + } } impl DefaultQueryFilters { - /// Set the [`ComponentId`] for the entity disabling marker - pub(crate) fn set_disabled(&mut self, component_id: ComponentId) -> Option<()> { - if self.disabled.is_some() { - return None; + /// Creates a new, completely empty [`DefaultQueryFilters`]. + /// + /// This is provided as an escape hatch; in most cases you should initialize this using [`FromWorld`], + /// which is automatically called when creating a new [`World`]. + #[must_use] + pub fn empty() -> Self { + DefaultQueryFilters { + disabling: SmallVec::new(), + } + } + + /// Adds this [`ComponentId`] to the set of [`DefaultQueryFilters`], + /// causing entities with this component to be excluded from queries. + /// + /// This method is idempotent, and will not add the same component multiple times. + /// + /// # Warning + /// + /// This method should only be called before the app starts, as it will not affect queries + /// initialized before it is called. + /// + /// As discussed in the [module docs](crate::entity_disabling), this can have performance implications, + /// as well as create interoperability issues, and should be used with caution. + pub fn register_disabling_component(&mut self, component_id: ComponentId) { + if !self.disabling.contains(&component_id) { + self.disabling.push(component_id); } - self.disabled = Some(component_id); - Some(()) } - /// Get an iterator over all currently enabled filter components - pub fn ids(&self) -> impl Iterator { - [self.disabled].into_iter().flatten() + /// Get an iterator over all of the components which disable entities when present. + pub fn disabling_ids(&self) -> impl Iterator + use<'_> { + self.disabling.iter().copied() } - pub(super) fn apply(&self, component_access: &mut FilteredAccess) { - for component_id in self.ids() { + /// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`]. + pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) { + for component_id in self.disabling_ids() { if !component_access.contains(component_id) { component_access.and_without(component_id); } @@ -70,7 +189,7 @@ impl DefaultQueryFilters { } pub(super) fn is_dense(&self, components: &Components) -> bool { - self.ids().all(|component_id| { + self.disabling_ids().all(|component_id| { components .get_info(component_id) .is_some_and(|info| info.storage_type() == StorageType::Table) @@ -82,24 +201,16 @@ impl DefaultQueryFilters { mod tests { use super::*; + use crate::{ + prelude::World, + query::{Has, With}, + }; use alloc::{vec, vec::Vec}; #[test] - fn test_set_filters() { - let mut filters = DefaultQueryFilters::default(); - assert_eq!(0, filters.ids().count()); - - assert!(filters.set_disabled(ComponentId::new(1)).is_some()); - assert!(filters.set_disabled(ComponentId::new(3)).is_none()); - - assert_eq!(1, filters.ids().count()); - assert_eq!(Some(ComponentId::new(1)), filters.ids().next()); - } - - #[test] - fn test_apply_filters() { - let mut filters = DefaultQueryFilters::default(); - filters.set_disabled(ComponentId::new(1)); + fn filters_modify_access() { + let mut filters = DefaultQueryFilters::empty(); + filters.register_disabling_component(ComponentId::new(1)); // A component access with an unrelated component let mut component_access = FilteredAccess::::default(); @@ -108,7 +219,7 @@ mod tests { .add_component_read(ComponentId::new(2)); let mut applied_access = component_access.clone(); - filters.apply(&mut applied_access); + filters.modify_access(&mut applied_access); assert_eq!(0, applied_access.with_filters().count()); assert_eq!( vec![ComponentId::new(1)], @@ -119,7 +230,7 @@ mod tests { component_access.and_with(ComponentId::new(4)); let mut applied_access = component_access.clone(); - filters.apply(&mut applied_access); + filters.modify_access(&mut applied_access); assert_eq!( vec![ComponentId::new(4)], applied_access.with_filters().collect::>() @@ -134,7 +245,7 @@ mod tests { component_access.and_with(ComponentId::new(1)); let mut applied_access = component_access.clone(); - filters.apply(&mut applied_access); + filters.modify_access(&mut applied_access); assert_eq!( vec![ComponentId::new(1), ComponentId::new(4)], applied_access.with_filters().collect::>() @@ -148,11 +259,46 @@ mod tests { .add_archetypal(ComponentId::new(1)); let mut applied_access = component_access.clone(); - filters.apply(&mut applied_access); + filters.modify_access(&mut applied_access); assert_eq!( vec![ComponentId::new(4)], applied_access.with_filters().collect::>() ); assert_eq!(0, applied_access.without_filters().count()); } + + #[derive(Component)] + struct CustomDisabled; + + #[test] + fn multiple_disabling_components() { + let mut world = World::new(); + world.register_disabling_component::(); + + world.spawn_empty(); + world.spawn(Disabled); + world.spawn(CustomDisabled); + world.spawn((Disabled, CustomDisabled)); + + let mut query = world.query::<()>(); + assert_eq!(1, query.iter(&world).count()); + + let mut query = world.query_filtered::<(), With>(); + assert_eq!(1, query.iter(&world).count()); + + let mut query = world.query::>(); + assert_eq!(2, query.iter(&world).count()); + + let mut query = world.query_filtered::<(), With>(); + assert_eq!(1, query.iter(&world).count()); + + let mut query = world.query::>(); + assert_eq!(2, query.iter(&world).count()); + + let mut query = world.query_filtered::<(), (With, With)>(); + assert_eq!(1, query.iter(&world).count()); + + let mut query = world.query::<(Has, Has)>(); + assert_eq!(4, query.iter(&world).count()); + } } diff --git a/crates/bevy_ecs/src/event/base.rs b/crates/bevy_ecs/src/event/base.rs index b9f7ec8d05579..42443d38b2fea 100644 --- a/crates/bevy_ecs/src/event/base.rs +++ b/crates/bevy_ecs/src/event/base.rs @@ -1,11 +1,9 @@ -use crate as bevy_ecs; +use crate::change_detection::MaybeLocation; use crate::component::ComponentId; use crate::world::World; use crate::{component::Component, traversal::Traversal}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ cmp::Ordering, fmt, @@ -106,8 +104,7 @@ pub struct EventId { // This value corresponds to the order in which each event was added to the world. pub id: usize, /// The source code location that triggered this event. - #[cfg(feature = "track_location")] - pub caller: &'static Location<'static>, + pub caller: MaybeLocation, #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] pub(super) _marker: PhantomData, } diff --git a/crates/bevy_ecs/src/event/collections.rs b/crates/bevy_ecs/src/event/collections.rs index 77573d2151b25..faa52ecd189af 100644 --- a/crates/bevy_ecs/src/event/collections.rs +++ b/crates/bevy_ecs/src/event/collections.rs @@ -1,11 +1,9 @@ -use crate as bevy_ecs; use alloc::vec::Vec; use bevy_ecs::{ + change_detection::MaybeLocation, event::{Event, EventCursor, EventId, EventInstance}, resource::Resource, }; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ marker::PhantomData, ops::{Deref, DerefMut}, @@ -124,21 +122,12 @@ impl Events { /// This method returns the [ID](`EventId`) of the sent `event`. #[track_caller] pub fn send(&mut self, event: E) -> EventId { - self.send_with_caller( - event, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.send_with_caller(event, MaybeLocation::caller()) } - pub(crate) fn send_with_caller( - &mut self, - event: E, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> EventId { + pub(crate) fn send_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId { let event_id = EventId { id: self.event_count, - #[cfg(feature = "track_location")] caller, _marker: PhantomData, }; @@ -308,8 +297,7 @@ impl Extend for Events { let events = iter.into_iter().map(|event| { let event_id = EventId { id: event_count, - #[cfg(feature = "track_location")] - caller: Location::caller(), + caller: MaybeLocation::caller(), _marker: PhantomData, }; event_count += 1; @@ -379,8 +367,7 @@ impl Iterator for SendBatchIds { let result = Some(EventId { id: self.last_count, - #[cfg(feature = "track_location")] - caller: Location::caller(), + caller: MaybeLocation::caller(), _marker: PhantomData, }); @@ -398,7 +385,7 @@ impl ExactSizeIterator for SendBatchIds { #[cfg(test)] mod tests { - use crate::{self as bevy_ecs, event::Events}; + use crate::event::Events; use bevy_ecs_macros::Event; #[test] diff --git a/crates/bevy_ecs/src/event/event_cursor.rs b/crates/bevy_ecs/src/event/event_cursor.rs index 262d4e3636adc..ff15ef4931581 100644 --- a/crates/bevy_ecs/src/event/event_cursor.rs +++ b/crates/bevy_ecs/src/event/event_cursor.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; use bevy_ecs::event::{ Event, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId, Events, }; diff --git a/crates/bevy_ecs/src/event/iterators.rs b/crates/bevy_ecs/src/event/iterators.rs index b2f9421aa7d0c..f9ee74b8b08d9 100644 --- a/crates/bevy_ecs/src/event/iterators.rs +++ b/crates/bevy_ecs/src/event/iterators.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; #[cfg(feature = "multi_threaded")] use bevy_ecs::batching::BatchingStrategy; use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events}; diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index c8bd24dcdde5f..9c19dc16895e7 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -31,7 +31,6 @@ pub use writer::EventWriter; #[cfg(test)] mod tests { - use crate as bevy_ecs; use alloc::{vec, vec::Vec}; use bevy_ecs::{event::*, system::assert_is_read_only_system}; use bevy_ecs_macros::Event; diff --git a/crates/bevy_ecs/src/event/mut_iterators.rs b/crates/bevy_ecs/src/event/mut_iterators.rs index f1434db062583..3cb531ce78829 100644 --- a/crates/bevy_ecs/src/event/mut_iterators.rs +++ b/crates/bevy_ecs/src/event/mut_iterators.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; #[cfg(feature = "multi_threaded")] use bevy_ecs::batching::BatchingStrategy; use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events}; diff --git a/crates/bevy_ecs/src/event/mutator.rs b/crates/bevy_ecs/src/event/mutator.rs index ee77f9961a3b4..0afbeaa00d896 100644 --- a/crates/bevy_ecs/src/event/mutator.rs +++ b/crates/bevy_ecs/src/event/mutator.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; #[cfg(feature = "multi_threaded")] use bevy_ecs::event::EventMutParIter; use bevy_ecs::{ diff --git a/crates/bevy_ecs/src/event/reader.rs b/crates/bevy_ecs/src/event/reader.rs index 1611fa6ba5ffc..bc0f4f86bcc99 100644 --- a/crates/bevy_ecs/src/event/reader.rs +++ b/crates/bevy_ecs/src/event/reader.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; #[cfg(feature = "multi_threaded")] use bevy_ecs::event::EventParIter; use bevy_ecs::{ diff --git a/crates/bevy_ecs/src/event/registry.rs b/crates/bevy_ecs/src/event/registry.rs index 070f08f846328..231f792f68392 100644 --- a/crates/bevy_ecs/src/event/registry.rs +++ b/crates/bevy_ecs/src/event/registry.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; use alloc::vec::Vec; use bevy_ecs::{ change_detection::{DetectChangesMut, MutUntyped}, diff --git a/crates/bevy_ecs/src/event/update.rs b/crates/bevy_ecs/src/event/update.rs index bf3c07de4d3c8..c7b43aef00689 100644 --- a/crates/bevy_ecs/src/event/update.rs +++ b/crates/bevy_ecs/src/event/update.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; use bevy_ecs::{ change_detection::Mut, component::Tick, diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index f391e7c3449da..91dd13898cc30 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -1,4 +1,3 @@ -use crate as bevy_ecs; use bevy_ecs::{ event::{Event, EventId, Events, SendBatchIds}, system::{ResMut, SystemParam}, @@ -15,14 +14,14 @@ use bevy_ecs::{ /// #[derive(Event)] /// pub struct MyEvent; // Custom event type. /// fn my_system(mut writer: EventWriter) { -/// writer.send(MyEvent); +/// writer.write(MyEvent); /// } /// /// # bevy_ecs::system::assert_is_system(my_system); /// ``` /// # Observers /// -/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically +/// "Buffered" Events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically /// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will /// be triggered, and if so, _when_ it will be triggered in the schedule. /// @@ -33,7 +32,7 @@ use bevy_ecs::{ /// /// # Untyped events /// -/// `EventWriter` can only send events of one specific type, which must be known at compile-time. +/// `EventWriter` can only write events of one specific type, which must be known at compile-time. /// This is not a problem most of the time, but you may find a situation where you cannot know /// ahead of time every kind of event you'll need to send. In this case, you can use the "type-erased event" pattern. /// @@ -65,13 +64,48 @@ pub struct EventWriter<'w, E: Event> { } impl<'w, E: Event> EventWriter<'w, E> { + /// Writes an `event`, which can later be read by [`EventReader`](super::EventReader)s. + /// This method returns the [ID](`EventId`) of the written `event`. + /// + /// See [`Events`] for details. + #[doc(alias = "send")] + #[track_caller] + pub fn write(&mut self, event: E) -> EventId { + self.events.send(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 written `events`. + /// + /// See [`Events`] for details. + #[doc(alias = "send_batch")] + #[track_caller] + pub fn write_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { + self.events.send_batch(events) + } + + /// Writes the default value of the event. Useful when the event is an empty struct. + /// This method returns the [ID](`EventId`) of the written `event`. + /// + /// See [`Events`] for details. + #[doc(alias = "send_default")] + #[track_caller] + pub fn write_default(&mut self) -> EventId + where + E: Default, + { + 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.events.send(event) + self.write(event) } /// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. @@ -79,20 +113,22 @@ impl<'w, E: Event> EventWriter<'w, E> { /// 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.events.send_batch(events) + 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.events.send_default() + self.write_default() } } diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index d4eb2475b95c9..2c4736d19b4a4 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -7,27 +7,24 @@ //! [`RelationshipTarget`]: crate::relationship::RelationshipTarget #[cfg(feature = "bevy_reflect")] -use crate::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; +use crate::reflect::{ReflectComponent, ReflectFromWorld}; use crate::{ - self as bevy_ecs, bundle::Bundle, component::{Component, HookContext}, - entity::{Entity, VisitEntities}, + entity::Entity, relationship::{RelatedSpawner, RelatedSpawnerCommands}, system::EntityCommands, world::{DeferredWorld, EntityWorldMut, FromWorld, World}, }; use alloc::{format, string::String, vec::Vec}; -use bevy_ecs_macros::VisitEntitiesMut; use core::ops::Deref; use core::slice; use disqualified::ShortName; use log::warn; -/// A [`Relationship`](crate::relationship::Relationship) component that creates the canonical +/// Stores the parent entity of this child entity with this component. +/// +/// This is a [`Relationship`](crate::relationship::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). /// @@ -36,7 +33,6 @@ use log::warn; /// 1. Organizing entities in a scene /// 2. Propagating configuration or data inherited from a parent, such as "visibility" or "world-space global transforms". /// 3. Ensuring a hierarchy is despawned when an entity is despawned. -/// 4. /// /// [`ChildOf`] contains a single "target" [`Entity`]. When [`ChildOf`] is inserted on a "source" entity, /// the "target" entity will automatically (and immediately, via a component hook) have a [`Children`] @@ -90,25 +86,18 @@ use log::warn; /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); /// ``` -#[derive(Component, Clone, VisitEntities, VisitEntitiesMut, PartialEq, Eq, Debug)] +#[derive(Component, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( feature = "bevy_reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) + reflect(Component, PartialEq, Debug, FromWorld) )] #[relationship(relationship_target = Children)] +#[doc(alias = "IsChild", alias = "Parent")] pub struct ChildOf(pub Entity); impl ChildOf { - /// Returns the "target" entity. + /// Returns the parent entity, which is the "target" of this relationship. pub fn get(&self) -> Entity { self.0 } @@ -134,18 +123,29 @@ impl FromWorld for ChildOf { } } -/// A [`RelationshipTarget`](crate::relationship::RelationshipTarget) collection component that is populated +/// 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. /// -/// Together, these components form the "canonical parent-child hierarchy". See the [`ChildOf`] component for all full +/// 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. -#[derive(Component, Default, VisitEntitiesMut, Debug, PartialEq, Eq)] -#[relationship_target(relationship = ChildOf, despawn_descendants)] +/// +/// # Usage +/// +/// Like all [`RelationshipTarget`] components, this data should not be directly manipulated to avoid desynchronization. +/// Instead, modify the [`ChildOf`] components on the "source" entities. +/// +/// To access the children of an entity, you can iterate over the [`Children`] component, +/// using the [`IntoIterator`] trait. +/// For more complex access patterns, see the [`RelationshipTarget`] trait. +/// +/// [`RelationshipTarget`]: crate::relationship::RelationshipTarget +#[derive(Component, Default, Debug, PartialEq, Eq)] +#[relationship_target(relationship = ChildOf, linked_spawn)] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "bevy_reflect", - reflect(Component, MapEntities, VisitEntities, VisitEntitiesMut, FromWorld) -)] +#[cfg_attr(feature = "bevy_reflect", reflect(Component, FromWorld))] +#[doc(alias = "IsParent")] pub struct Children(Vec); impl<'a> IntoIterator for &'a Children { @@ -293,12 +293,49 @@ pub fn validate_parent_has_component( } } +/// Returns a [`SpawnRelatedBundle`] that will insert the [`Children`] component, spawn a [`SpawnableList`] of entities with given bundles that +/// relate to the [`Children`] entity via the [`ChildOf`] component, and reserve space in the [`Children`] for each spawned entity. +/// +/// Any additional arguments will be interpreted as bundles to be spawned. +/// +/// Also see [`related`](crate::related) for a version of this that works with any [`RelationshipTarget`] type. +/// +/// ``` +/// # use bevy_ecs::hierarchy::Children; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::world::World; +/// # use bevy_ecs::children; +/// # use bevy_ecs::spawn::{Spawn, SpawnRelated}; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// children![ +/// Name::new("Child1"), +/// ( +/// Name::new("Child2"), +/// children![Name::new("Grandchild")] +/// ) +/// ] +/// )); +/// ``` +/// +/// [`RelationshipTarget`]: crate::relationship::RelationshipTarget +/// [`SpawnRelatedBundle`]: crate::spawn::SpawnRelatedBundle +/// [`SpawnableList`]: crate::spawn::SpawnableList +#[macro_export] +macro_rules! children { + [$($child:expr),*$(,)?] => { + $crate::hierarchy::Children::spawn(($($crate::spawn::Spawn($child)),*)) + }; +} + #[cfg(test)] mod tests { use crate::{ entity::Entity, hierarchy::{ChildOf, Children}, relationship::RelationshipTarget, + spawn::{Spawn, SpawnRelated}, world::World, }; use alloc::{vec, vec::Vec}; @@ -451,4 +488,11 @@ mod tests { "ChildOf should still be there" ); } + + #[test] + fn spawn_children() { + let mut world = World::new(); + let id = world.spawn(Children::spawn((Spawn(()), Spawn(())))).id(); + assert_eq!(world.entity(id).get::().unwrap().len(), 2,); + } } diff --git a/crates/bevy_ecs/src/identifier/mod.rs b/crates/bevy_ecs/src/identifier/mod.rs index cf467da505dfc..964f03ed3a856 100644 --- a/crates/bevy_ecs/src/identifier/mod.rs +++ b/crates/bevy_ecs/src/identifier/mod.rs @@ -201,7 +201,7 @@ mod tests { // and also Entity flag. let high = 0x7FFFFFFF; let low = 0xC; - let bits: u64 = high << u32::BITS | low; + let bits: u64 = (high << u32::BITS) | low; let id = Identifier::try_from_bits(bits).unwrap(); diff --git a/crates/bevy_ecs/src/label.rs b/crates/bevy_ecs/src/label.rs index 10a23cfb898c3..d57ec621fbbad 100644 --- a/crates/bevy_ecs/src/label.rs +++ b/crates/bevy_ecs/src/label.rs @@ -180,7 +180,7 @@ macro_rules! define_label { impl $crate::intern::Internable for dyn $label_trait_name { fn leak(&self) -> &'static Self { - Box::leak(self.dyn_clone()) + $crate::label::Box::leak(self.dyn_clone()) } fn ref_eq(&self, other: &Self) -> bool { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index ae325aa0857e3..26b888f2302ce 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -33,6 +33,9 @@ compile_error!("bevy_ecs cannot safely compile for a 16-bit platform."); extern crate alloc; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_ecs; + pub mod archetype; pub mod batching; pub mod bundle; @@ -55,6 +58,7 @@ pub mod removal_detection; pub mod resource; pub mod result; pub mod schedule; +pub mod spawn; pub mod storage; pub mod system; pub mod traversal; @@ -74,13 +78,16 @@ pub mod prelude { pub use crate::{ bundle::Bundle, change_detection::{DetectChanges, DetectChangesMut, Mut, Ref}, + children, component::{require, Component}, entity::{Entity, EntityBorrow, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children}, name::{Name, NameOrEntity}, - observer::{CloneEntityWithObserversExt, Observer, Trigger}, + observer::{Observer, Trigger}, query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, + related, + relationship::RelationshipTarget, removal_detection::RemovedComponents, resource::Resource, result::{Error, Result}, @@ -88,6 +95,7 @@ pub mod prelude { apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfigs, Schedule, Schedules, SystemSet, }, + spawn::{Spawn, SpawnRelated}, system::{ Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem, @@ -128,7 +136,6 @@ pub mod __macro_exports { #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{ bundle::Bundle, change_detection::Ref, @@ -229,13 +236,9 @@ mod tests { y: SparseStored, } let mut ids = Vec::new(); - ::component_ids( - &mut world.components, - &mut world.storages, - &mut |id| { - ids.push(id); - }, - ); + ::component_ids(&mut world.components, &mut |id| { + ids.push(id); + }); assert_eq!( ids, @@ -283,13 +286,9 @@ mod tests { } let mut ids = Vec::new(); - ::component_ids( - &mut world.components, - &mut world.storages, - &mut |id| { - ids.push(id); - }, - ); + ::component_ids(&mut world.components, &mut |id| { + ids.push(id); + }); assert_eq!( ids, @@ -339,13 +338,9 @@ mod tests { } let mut ids = Vec::new(); - ::component_ids( - &mut world.components, - &mut world.storages, - &mut |id| { - ids.push(id); - }, - ); + ::component_ids(&mut world.components, &mut |id| { + ids.push(id); + }); assert_eq!(ids, &[world.register_component::(),]); @@ -2675,6 +2670,102 @@ mod tests { World::new().register_component::(); } + #[test] + fn visit_struct_entities() { + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + struct Foo(usize, #[entities] Entity); + + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + struct Bar { + #[entities] + a: Entity, + b: usize, + #[entities] + c: Vec, + } + + let mut world = World::new(); + let e1 = world.spawn_empty().id(); + let e2 = world.spawn_empty().id(); + 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 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]); + } + + #[test] + fn visit_enum_entities() { + #[derive(Component)] + #[expect( + unused, + reason = "extra fields are used to ensure the derive works properly" + )] + enum Foo { + Bar(usize, #[entities] Entity), + Baz { + #[entities] + a: Entity, + b: usize, + #[entities] + c: Vec, + }, + } + + let mut world = World::new(); + let e1 = world.spawn_empty().id(); + let e2 = world.spawn_empty().id(); + 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 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]); + } + #[expect( dead_code, reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index c5557bc409cf8..9c38b28481e7f 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -1,6 +1,6 @@ //! Provides the [`Name`] [`Component`], used for identifying an [`Entity`]. -use crate::{self as bevy_ecs, component::Component, entity::Entity, query::QueryData}; +use crate::{component::Component, entity::Entity, query::QueryData}; use alloc::{ borrow::{Cow, ToOwned}, diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 1ad30890e494a..556ca0d8aa523 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,10 +1,11 @@ use crate::{ component::{ - Component, ComponentCloneHandler, ComponentHooks, HookContext, Mutable, StorageType, + Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, }, - entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, + entity::{ComponentCloneCtx, Entity, EntityClonerBuilder}, observer::ObserverState, - world::{DeferredWorld, World}, + system::Commands, + world::World, }; use alloc::vec::Vec; @@ -16,8 +17,8 @@ impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_remove(|mut world, HookContext { entity, .. }| { + fn on_remove() -> Option { + Some(|mut world, HookContext { entity, .. }| { let observed_by = { let mut component = world.get_mut::(entity).unwrap(); core::mem::take(&mut component.0) @@ -42,37 +43,32 @@ impl Component for ObservedBy { world.commands().entity(e).despawn(); } } - }); + }) } - fn get_component_clone_handler() -> ComponentCloneHandler { - ComponentCloneHandler::ignore() + fn clone_behavior() -> ComponentCloneBehavior { + ComponentCloneBehavior::Ignore } } -/// Trait that holds functions for configuring interaction with observers during entity cloning. -pub trait CloneEntityWithObserversExt { +impl EntityClonerBuilder<'_> { /// Sets the option to automatically add cloned entities to the observers targeting source entity. - fn add_observers(&mut self, add_observers: bool) -> &mut Self; -} - -impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> { - fn add_observers(&mut self, add_observers: bool) -> &mut Self { + pub fn add_observers(&mut self, add_observers: bool) -> &mut Self { if add_observers { - self.override_component_clone_handler::( - ComponentCloneHandler::custom_handler(component_clone_observed_by), - ) + self.override_clone_behavior::(ComponentCloneBehavior::Custom( + component_clone_observed_by, + )) } else { - self.remove_component_clone_handler_override::() + self.remove_clone_behavior_override::() } } } -fn component_clone_observed_by(world: &mut DeferredWorld, ctx: &mut ComponentCloneCtx) { +fn component_clone_observed_by(commands: &mut Commands, ctx: &mut ComponentCloneCtx) { let target = ctx.target(); let source = ctx.source(); - world.commands().queue(move |world: &mut World| { + commands.queue(move |world: &mut World| { let observed_by = world .get::(source) .map(|observed_by| observed_by.0.clone()) @@ -114,12 +110,7 @@ fn component_clone_observed_by(world: &mut DeferredWorld, ctx: &mut ComponentClo #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, - entity::EntityCloneBuilder, - event::Event, - observer::{CloneEntityWithObserversExt, Trigger}, - resource::Resource, - system::ResMut, + entity::EntityCloner, event::Event, observer::Trigger, resource::Resource, system::ResMut, world::World, }; @@ -143,9 +134,9 @@ mod tests { world.trigger_targets(E, e); let e_clone = world.spawn_empty().id(); - let mut builder = EntityCloneBuilder::new(&mut world); - builder.add_observers(true); - builder.clone_entity(e, e_clone); + EntityCloner::build(&mut world) + .add_observers(true) + .clone_entity(e, e_clone); world.trigger_targets(E, [e, e_clone]); diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 1fd58610fb00a..c44e961368985 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -3,11 +3,12 @@ mod entity_observer; mod runner; -pub use entity_observer::{CloneEntityWithObserversExt, ObservedBy}; +pub use entity_observer::ObservedBy; pub use runner::*; use crate::{ archetype::ArchetypeFlags, + change_detection::MaybeLocation, component::ComponentId, entity::hash_map::EntityHashMap, prelude::*, @@ -24,9 +25,6 @@ use core::{ }; use smallvec::SmallVec; -#[cfg(feature = "track_location")] -use core::panic::Location; - /// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the /// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also /// contains event propagation information. See [`Trigger::propagate`] for more information. @@ -143,8 +141,7 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> { } /// Returns the source code location that triggered this observer. - #[cfg(feature = "track_location")] - pub fn caller(&self) -> &'static Location<'static> { + pub fn caller(&self) -> MaybeLocation { self.trigger.caller } } @@ -335,10 +332,8 @@ 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. - #[cfg(feature = "track_location")] - pub caller: &'static Location<'static>, + pub caller: MaybeLocation, } impl ObserverTrigger { @@ -415,7 +410,7 @@ impl Observers { components: impl Iterator + Clone, data: &mut T, propagate: &mut bool, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { // SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld` let (mut world, observers) = unsafe { @@ -440,7 +435,6 @@ impl Observers { event_type, components: components.clone().collect(), target, - #[cfg(feature = "track_location")] caller, }, data.into(), @@ -565,28 +559,14 @@ impl World { /// If you need to use the event after triggering it, use [`World::trigger_ref`] instead. #[track_caller] pub fn trigger(&mut self, event: E) { - self.trigger_with_caller( - event, - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.trigger_with_caller(event, MaybeLocation::caller()); } - pub(crate) fn trigger_with_caller( - &mut self, - mut event: E, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) { + pub(crate) fn trigger_with_caller(&mut self, mut event: E, caller: MaybeLocation) { let event_id = E::register_component_id(self); // SAFETY: We just registered `event_id` with the type of `event` unsafe { - self.trigger_targets_dynamic_ref_with_caller( - event_id, - &mut event, - (), - #[cfg(feature = "track_location")] - caller, - ); + self.trigger_targets_dynamic_ref_with_caller(event_id, &mut event, (), caller); } } @@ -608,30 +588,19 @@ impl World { /// If you need to use the event after triggering it, use [`World::trigger_targets_ref`] instead. #[track_caller] pub fn trigger_targets(&mut self, event: E, targets: impl TriggerTargets) { - self.trigger_targets_with_caller( - event, - targets, - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.trigger_targets_with_caller(event, targets, MaybeLocation::caller()); } pub(crate) fn trigger_targets_with_caller( &mut self, mut event: E, targets: impl TriggerTargets, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { let event_id = E::register_component_id(self); // SAFETY: We just registered `event_id` with the type of `event` unsafe { - self.trigger_targets_dynamic_ref_with_caller( - event_id, - &mut event, - targets, - #[cfg(feature = "track_location")] - caller, - ); + self.trigger_targets_dynamic_ref_with_caller(event_id, &mut event, targets, caller); } } @@ -689,8 +658,7 @@ impl World { event_id, event_data, targets, - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); } @@ -702,7 +670,7 @@ impl World { event_id: ComponentId, event_data: &mut E, targets: Targets, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { let mut world = DeferredWorld::from(self); if targets.entities().is_empty() { @@ -714,7 +682,6 @@ impl World { targets.components(), event_data, false, - #[cfg(feature = "track_location")] caller, ); }; @@ -728,7 +695,6 @@ impl World { targets.components(), event_data, E::AUTO_PROPAGATE, - #[cfg(feature = "track_location")] caller, ); }; @@ -858,15 +824,13 @@ impl World { #[cfg(test)] mod tests { use alloc::{vec, vec::Vec}; - #[cfg(feature = "track_location")] - use core::panic::Location; use bevy_platform_support::collections::HashMap; use bevy_ptr::OwningPtr; - use crate as bevy_ecs; use crate::component::ComponentId; use crate::{ + change_detection::MaybeLocation, observer::{Observer, ObserverDescriptor, ObserverState, OnReplace}, prelude::*, traversal::Traversal, @@ -1616,13 +1580,12 @@ mod tests { } #[test] - #[cfg(feature = "track_location")] #[track_caller] fn observer_caller_location_event() { #[derive(Event)] struct EventA; - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let mut world = World::new(); world.add_observer(move |trigger: Trigger| { assert_eq!(trigger.caller(), caller); @@ -1631,13 +1594,12 @@ mod tests { } #[test] - #[cfg(feature = "track_location")] #[track_caller] fn observer_caller_location_command_archetype_move() { #[derive(Component)] struct Component; - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let mut world = World::new(); world.add_observer(move |trigger: Trigger| { assert_eq!(trigger.caller(), caller); diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 11ff5eabbc1e9..fdb3b4fa800ba 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,10 +2,11 @@ use alloc::{boxed::Box, vec, vec::Vec}; use core::any::Any; use crate::{ - component::{ComponentHook, ComponentHooks, ComponentId, HookContext, Mutable, StorageType}, + component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, + result::{DefaultSystemErrorHandler, SystemErrorContext}, system::{IntoObserverSystem, ObserverSystem}, world::DeferredWorld, }; @@ -65,13 +66,16 @@ impl Component for ObserverState { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|mut world, HookContext { entity, .. }| { + fn on_add() -> Option { + Some(|mut world, HookContext { entity, .. }| { world.commands().queue(move |world: &mut World| { world.register_observer(entity); }); - }); - hooks.on_remove(|mut world, HookContext { entity, .. }| { + }) + } + + fn on_remove() -> Option { + Some(|mut world, HookContext { entity, .. }| { let descriptor = core::mem::take( &mut world .entity_mut(entity) @@ -83,7 +87,7 @@ impl Component for ObserverState { world.commands().queue(move |world: &mut World| { world.unregister_observer(entity, descriptor); }); - }); + }) } } @@ -269,6 +273,7 @@ pub struct Observer { system: Box, descriptor: ObserverDescriptor, hook_on_add: ComponentHook, + error_handler: Option, } impl Observer { @@ -279,6 +284,7 @@ impl Observer { system: Box::new(IntoObserverSystem::into_system(system)), descriptor: Default::default(), hook_on_add: hook_on_add::, + error_handler: None, } } @@ -313,6 +319,14 @@ impl Observer { self } + /// Set the error handler to use for this observer. + /// + /// See the [`result` module-level documentation](crate::result) for more information. + pub fn with_error_handler(mut self, error_handler: fn(Error, SystemErrorContext)) -> Self { + self.error_handler = Some(error_handler); + self + } + /// Returns the [`ObserverDescriptor`] for this [`Observer`]. pub fn descriptor(&self) -> &ObserverDescriptor { &self.descriptor @@ -322,14 +336,14 @@ impl Observer { impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|world, context| { + fn on_add() -> Option { + Some(|world, context| { let Some(observe) = world.get::(context.entity) else { return; }; let hook = observe.hook_on_add; hook(world, context); - }); + }) } } @@ -360,6 +374,15 @@ 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() }, @@ -383,13 +406,21 @@ fn observer_system_runner>( unsafe { (*system).update_archetype_component_access(world); if (*system).validate_param_unsafe(world) { - (*system).run_unsafe(trigger, 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()); } } } -/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`ComponentHooks::on_add`). +/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::component::ComponentHooks::on_add`). /// /// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters /// erased. @@ -404,7 +435,7 @@ fn hook_on_add>( world.commands().queue(move |world: &mut World| { let event_id = E::register_component_id(world); let mut components = Vec::new(); - B::component_ids(&mut world.components, &mut world.storages, &mut |id| { + B::component_ids(&mut world.components, &mut |id| { components.push(id); }); let mut descriptor = ObserverDescriptor { @@ -413,10 +444,15 @@ fn hook_on_add>( ..Default::default() }; + 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 { @@ -439,3 +475,44 @@ fn hook_on_add>( } }); } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{event::Event, observer::Trigger}; + + #[derive(Event)] + struct TriggerEvent; + + #[test] + #[should_panic(expected = "I failed!")] + fn test_fallible_observer() { + fn system(_: Trigger) -> Result { + Err("I failed!".into()) + } + + let mut world = World::default(); + world.add_observer(system); + Schedule::default().run(&mut world); + world.trigger(TriggerEvent); + } + + #[test] + fn test_fallible_observer_ignored_errors() { + #[derive(Resource, Default)] + struct Ran(bool); + + fn system(_: Trigger, mut ran: ResMut) -> Result { + ran.0 = true; + Err("I failed!".into()) + } + + let mut world = World::default(); + world.init_resource::(); + let observer = Observer::new(system).with_error_handler(crate::result::ignore); + world.spawn(observer); + Schedule::default().run(&mut world); + world.trigger(TriggerEvent); + assert!(world.resource::().0); + } +} diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index d699cb3aea4ac..65ad02111cad8 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -275,7 +275,6 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{prelude::*, world::FilteredEntityRef}; use std::dbg; diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index 2031f390009cc..f1f9ddfe21993 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; use crate::{ - entity::{Entity, EntityDoesNotExistDetails}, + entity::{Entity, EntityDoesNotExistError}, world::unsafe_world_cell::UnsafeWorldCell, }; @@ -14,13 +14,19 @@ pub enum QueryEntityError<'w> { /// Either it does not have a requested component, or it has a component which the query filters out. QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>), /// The given [`Entity`] does not exist. - NoSuchEntity(Entity, EntityDoesNotExistDetails), + EntityDoesNotExist(EntityDoesNotExistError), /// The [`Entity`] was requested mutably more than once. /// - /// See [`QueryState::get_many_mut`](crate::query::QueryState::get_many_mut) for an example. + /// See [`Query::get_many_mut`](crate::system::Query::get_many_mut) for an example. AliasedMutability(Entity), } +impl<'w> From for QueryEntityError<'w> { + fn from(error: EntityDoesNotExistError) -> Self { + QueryEntityError::EntityDoesNotExist(error) + } +} + impl<'w> core::error::Error for QueryEntityError<'w> {} impl<'w> core::fmt::Display for QueryEntityError<'w> { @@ -33,8 +39,8 @@ impl<'w> core::fmt::Display for QueryEntityError<'w> { )?; format_archetype(f, world, entity) } - Self::NoSuchEntity(entity, details) => { - write!(f, "The entity with ID {entity} {details}") + Self::EntityDoesNotExist(error) => { + write!(f, "{error}") } Self::AliasedMutability(entity) => { write!( @@ -54,8 +60,8 @@ impl<'w> core::fmt::Debug for QueryEntityError<'w> { format_archetype(f, world, entity)?; write!(f, ")") } - Self::NoSuchEntity(entity, details) => { - write!(f, "NoSuchEntity({entity} {details})") + Self::EntityDoesNotExist(error) => { + write!(f, "EntityDoesNotExist({error})") } Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"), } @@ -88,7 +94,7 @@ impl<'w> PartialEq for QueryEntityError<'w> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::QueryDoesNotMatch(e1, _), Self::QueryDoesNotMatch(e2, _)) if e1 == e2 => true, - (Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true, + (Self::EntityDoesNotExist(e1), Self::EntityDoesNotExist(e2)) if e1 == e2 => true, (Self::AliasedMutability(e1), Self::AliasedMutability(e2)) if e1 == e2 => true, _ => false, } @@ -111,7 +117,6 @@ pub enum QuerySingleError { #[cfg(test)] mod test { - use crate as bevy_ecs; use crate::prelude::World; use alloc::format; use bevy_ecs_macros::Component; diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 31818bd44ae9a..08e06d03f4478 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundle, - change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut}, + change_detection::{MaybeLocation, Ticks, TicksMut}, component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, @@ -12,7 +12,7 @@ use crate::{ }, }; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; -use core::{cell::UnsafeCell, marker::PhantomData}; +use core::{cell::UnsafeCell, marker::PhantomData, panic::Location}; use smallvec::SmallVec; use variadics_please::all_tuples; @@ -278,6 +278,31 @@ use variadics_please::all_tuples; pub unsafe trait QueryData: WorldQuery { /// The read-only variant of this [`QueryData`], which satisfies the [`ReadOnlyQueryData`] trait. type ReadOnly: ReadOnlyQueryData::State>; + + /// The item returned by this [`WorldQuery`] + /// This will be the data retrieved by the query, + /// and is visible to the end user when calling e.g. `Query::get`. + type Item<'a>; + + /// This function manually implements subtyping for the query items. + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>; + + /// 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 + /// [`WorldQuery::set_archetype`] with an `entity` in the current archetype. + /// Accesses components registered in [`WorldQuery::update_component_access`]. + /// + /// # Safety + /// + /// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and + /// `table_row` must be in the range of the current table and archetype. + /// - There must not be simultaneous conflicting component access registered in `update_component_access`. + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w>; } /// A [`QueryData`] that is read only. @@ -288,7 +313,7 @@ pub unsafe trait QueryData: WorldQuery { pub unsafe trait ReadOnlyQueryData: QueryData {} /// The item type returned when a [`WorldQuery`] is iterated over -pub type QueryItem<'w, Q> = ::Item<'w>; +pub type QueryItem<'w, Q> = ::Item<'w>; /// The read-only variant of the item type returned when a [`QueryData`] is iterated over immutably pub type ROQueryItem<'w, D> = QueryItem<'w, ::ReadOnly>; @@ -296,14 +321,9 @@ pub type ROQueryItem<'w, D> = QueryItem<'w, ::ReadOnly>; /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Entity { - type Item<'w> = Entity; type Fetch<'w> = (); type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} unsafe fn init_fetch<'w>( @@ -329,15 +349,6 @@ unsafe impl WorldQuery for Entity { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - entity - } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -357,6 +368,21 @@ unsafe impl WorldQuery for Entity { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for Entity { type ReadOnly = Self; + + type Item<'w> = Entity; + + 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> { + entity + } } /// SAFETY: access is read only @@ -366,14 +392,9 @@ unsafe impl ReadOnlyQueryData for Entity {} /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for EntityLocation { - type Item<'w> = EntityLocation; type Fetch<'w> = &'w Entities; type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -404,16 +425,6 @@ unsafe impl WorldQuery for EntityLocation { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - #[inline(always)] - unsafe fn 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 - unsafe { fetch.get(entity).debug_checked_unwrap() } - } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -433,6 +444,21 @@ unsafe impl WorldQuery for EntityLocation { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for EntityLocation { type ReadOnly = Self; + type Item<'w> = EntityLocation; + + 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: `fetch` must be called with an entity that exists in the world + unsafe { fetch.get(entity).debug_checked_unwrap() } + } } /// SAFETY: access is read only @@ -443,14 +469,9 @@ unsafe impl ReadOnlyQueryData for EntityLocation {} /// 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 Item<'w> = EntityRef<'w>; type Fetch<'w> = UnsafeWorldCell<'w>; type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -479,18 +500,6 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - #[inline(always)] - unsafe fn fetch<'w>( - world: &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() }; - // SAFETY: Read-only access to every component has been registered. - unsafe { EntityRef::new(cell) } - } - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { assert!( !access.access().has_any_component_write(), @@ -516,6 +525,23 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl<'a> QueryData for EntityRef<'a> { type ReadOnly = Self; + type Item<'w> = EntityRef<'w>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + world: &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() }; + // SAFETY: Read-only access to every component has been registered. + unsafe { EntityRef::new(cell) } + } } /// SAFETY: access is read only @@ -523,14 +549,9 @@ 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 Item<'w> = EntityMut<'w>; type Fetch<'w> = UnsafeWorldCell<'w>; type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -559,18 +580,6 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - #[inline(always)] - unsafe fn fetch<'w>( - world: &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() }; - // SAFETY: mutable access to every component has been registered. - unsafe { EntityMut::new(cell) } - } - fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) { assert!( !access.access().has_any_component_read(), @@ -596,18 +605,30 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> { /// SAFETY: access of `EntityRef` is a subset of `EntityMut` unsafe impl<'a> QueryData for EntityMut<'a> { type ReadOnly = EntityRef<'a>; + type Item<'w> = EntityMut<'w>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + world: &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() }; + // SAFETY: mutable access to every component has been registered. + unsafe { EntityMut::new(cell) } + } } /// 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 Item<'w> = FilteredEntityRef<'w>; type State = FilteredAccess; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -646,18 +667,6 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { state.access_mut().clear_writes(); } - #[inline(always)] - unsafe fn fetch<'w>( - (world, 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() }; - // SAFETY: mutable access to every component has been registered. - unsafe { FilteredEntityRef::new(cell, access.clone()) } - } - fn update_component_access( state: &Self::State, filtered_access: &mut FilteredAccess, @@ -688,6 +697,23 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl<'a> QueryData for FilteredEntityRef<'a> { type ReadOnly = Self; + type Item<'w> = FilteredEntityRef<'w>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + (world, 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() }; + // SAFETY: mutable access to every component has been registered. + unsafe { FilteredEntityRef::new(cell, access.clone()) } + } } /// SAFETY: Access is read-only. @@ -696,13 +722,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 Item<'w> = FilteredEntityMut<'w>; type State = FilteredAccess; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -740,18 +761,6 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { state.clone_from(access); } - #[inline(always)] - unsafe fn fetch<'w>( - (world, 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() }; - // SAFETY: mutable access to every component has been registered. - unsafe { FilteredEntityMut::new(cell, access.clone()) } - } - fn update_component_access( state: &Self::State, filtered_access: &mut FilteredAccess, @@ -782,6 +791,23 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { /// SAFETY: access of `FilteredEntityRef` is a subset of `FilteredEntityMut` unsafe impl<'a> QueryData for FilteredEntityMut<'a> { type ReadOnly = FilteredEntityRef<'a>; + type Item<'w> = FilteredEntityMut<'w>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + (world, 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() }; + // SAFETY: mutable access to every component has been registered. + unsafe { FilteredEntityMut::new(cell, access.clone()) } + } } /// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B` @@ -792,13 +818,8 @@ where B: Bundle, { type Fetch<'w> = UnsafeWorldCell<'w>; - type Item<'w> = EntityRefExcept<'w, B>; type State = SmallVec<[ComponentId; 4]>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -824,15 +845,6 @@ where unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {} - unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, - entity: Entity, - _: TableRow, - ) -> Self::Item<'w> { - let cell = world.get_entity(entity).unwrap(); - EntityRefExcept::new(cell) - } - fn update_component_access( state: &Self::State, filtered_access: &mut FilteredAccess, @@ -877,6 +889,20 @@ where B: Bundle, { type ReadOnly = Self; + type Item<'w> = EntityRefExcept<'w, B>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + unsafe fn fetch<'w>( + world: &mut Self::Fetch<'w>, + entity: Entity, + _: TableRow, + ) -> Self::Item<'w> { + let cell = world.get_entity(entity).unwrap(); + EntityRefExcept::new(cell) + } } /// SAFETY: `EntityRefExcept` enforces read-only access to its contained @@ -891,13 +917,8 @@ where B: Bundle, { type Fetch<'w> = UnsafeWorldCell<'w>; - type Item<'w> = EntityMutExcept<'w, B>; type State = SmallVec<[ComponentId; 4]>; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -923,15 +944,6 @@ where unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {} - unsafe fn fetch<'w>( - world: &mut Self::Fetch<'w>, - entity: Entity, - _: TableRow, - ) -> Self::Item<'w> { - let cell = world.get_entity(entity).unwrap(); - EntityMutExcept::new(cell) - } - fn update_component_access( state: &Self::State, filtered_access: &mut FilteredAccess, @@ -977,20 +989,29 @@ where B: Bundle, { type ReadOnly = EntityRefExcept<'a, B>; + type Item<'w> = EntityMutExcept<'w, B>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + unsafe fn fetch<'w>( + world: &mut Self::Fetch<'w>, + entity: Entity, + _: TableRow, + ) -> Self::Item<'w> { + let cell = world.get_entity(entity).unwrap(); + EntityMutExcept::new(cell) + } } /// SAFETY: /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for &Archetype { - type Item<'w> = &'w Archetype; type Fetch<'w> = (&'w Entities, &'w Archetypes); type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1021,19 +1042,6 @@ unsafe impl WorldQuery for &Archetype { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - let (entities, archetypes) = *fetch; - // SAFETY: `fetch` must be called with an entity that exists in the world - let location = unsafe { entities.get(entity).debug_checked_unwrap() }; - // SAFETY: The assigned archetype for a living entity must always be valid. - unsafe { archetypes.get(location.archetype_id).debug_checked_unwrap() } - } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) {} @@ -1053,6 +1061,24 @@ unsafe impl WorldQuery for &Archetype { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for &Archetype { type ReadOnly = Self; + type Item<'w> = &'w Archetype; + + 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> { + let (entities, archetypes) = *fetch; + // SAFETY: `fetch` must be called with an entity that exists in the world + let location = unsafe { entities.get(entity).debug_checked_unwrap() }; + // SAFETY: The assigned archetype for a living entity must always be valid. + unsafe { archetypes.get(location.archetype_id).debug_checked_unwrap() } + } } /// SAFETY: access is read only @@ -1065,7 +1091,7 @@ pub struct ReadFetch<'w, T: Component> { // T::STORAGE_TYPE = StorageType::Table Option>>, // T::STORAGE_TYPE = StorageType::SparseSet - &'w ComponentSparseSet, + Option<&'w ComponentSparseSet>, >, } @@ -1082,14 +1108,9 @@ impl Copy for ReadFetch<'_, T> {} /// `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 { - type Item<'w> = &'w T; type Fetch<'w> = ReadFetch<'w, T>; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1109,13 +1130,7 @@ unsafe impl WorldQuery for &T { // which we are allowed to access since we registered it in `update_archetype_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) - .debug_checked_unwrap() - } + unsafe { world.storages().sparse_sets.get(component_id) } }, ), } @@ -1159,28 +1174,6 @@ unsafe impl WorldQuery for &T { unsafe { fetch.components.set_table(table_data) }; } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - fetch.components.extract( - |table| { - // 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()) }; - item.deref() - }, - |sparse_set| { - // SAFETY: Caller ensures `entity` is in range. - let item = unsafe { sparse_set.get(entity).debug_checked_unwrap() }; - item.deref() - }, - ) - } - fn update_component_access( &component_id: &ComponentId, access: &mut FilteredAccess, @@ -1212,6 +1205,38 @@ unsafe impl WorldQuery for &T { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for &T { type ReadOnly = Self; + type Item<'w> = &'w T; + + fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + fetch.components.extract( + |table| { + // 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()) }; + item.deref() + }, + |sparse_set| { + // SAFETY: Caller ensures `entity` is in range. + let item = unsafe { + sparse_set + .debug_checked_unwrap() + .get(entity) + .debug_checked_unwrap() + }; + item.deref() + }, + ) + } } /// SAFETY: access is read only @@ -1226,10 +1251,11 @@ pub struct RefFetch<'w, T: Component> { ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, - MaybeThinSlicePtrLocation<'w>, + MaybeLocation>>>, )>, // T::STORAGE_TYPE = StorageType::SparseSet - &'w ComponentSparseSet, + // Can be `None` when the component has never been inserted + Option<&'w ComponentSparseSet>, >, last_run: Tick, this_run: Tick, @@ -1248,14 +1274,9 @@ impl Copy for RefFetch<'_, T> {} /// `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> { - type Item<'w> = Ref<'w, T>; type Fetch<'w> = RefFetch<'w, T>; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1275,13 +1296,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { // which we are allowed to access since we registered it in `update_archetype_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) - .debug_checked_unwrap() - } + unsafe { world.storages().sparse_sets.get(component_id) } }, ), last_run, @@ -1322,15 +1337,51 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { 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(), - #[cfg(feature = "track_location")] - column.get_changed_by_slice(table.entity_count()).into(), - #[cfg(not(feature = "track_location"))] - (), + column + .get_changed_by_slice(table.entity_count()) + .map(Into::into), )); // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table unsafe { fetch.components.set_table(table_data) }; } + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { + assert!( + !access.access().has_component_write(component_id), + "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + core::any::type_name::(), + ); + access.add_component_read(component_id); + } + + fn init_state(world: &mut World) -> ComponentId { + world.register_component::() + } + + fn get_state(components: &Components) -> Option { + components.component_id::() + } + + fn matches_component_set( + &state: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state) + } +} + +/// SAFETY: `Self` is the same as `Self::ReadOnly` +unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { + type ReadOnly = Self; + type Item<'w> = Ref<'w, T>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> { + item + } + #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, @@ -1340,7 +1391,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { fetch.components.extract( |table| { // SAFETY: set_table was previously called - let (table_components, added_ticks, changed_ticks, _callers) = + let (table_components, added_ticks, changed_ticks, callers) = unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. @@ -1350,8 +1401,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { // SAFETY: The caller ensures `table_row` is in range. let changed = unsafe { changed_ticks.get(table_row.as_usize()) }; // SAFETY: The caller ensures `table_row` is in range. - #[cfg(feature = "track_location")] - let caller = unsafe { _callers.get(table_row.as_usize()) }; + let caller = callers.map(|callers| unsafe { callers.get(table_row.as_usize()) }); Ref { value: component.deref(), @@ -1361,56 +1411,26 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { this_run: fetch.this_run, last_run: fetch.last_run, }, - #[cfg(feature = "track_location")] - changed_by: caller.deref(), + changed_by: caller.map(|caller| caller.deref()), } }, |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let (component, ticks, _caller) = - unsafe { sparse_set.get_with_ticks(entity).debug_checked_unwrap() }; + // SAFETY: The caller ensures `entity` is in range and has the component. + let (component, ticks, caller) = unsafe { + sparse_set + .debug_checked_unwrap() + .get_with_ticks(entity) + .debug_checked_unwrap() + }; Ref { value: component.deref(), ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run), - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), } }, ) } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_component_write(component_id), - "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - core::any::type_name::(), - ); - access.add_component_read(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { - type ReadOnly = Self; } /// SAFETY: access is read only @@ -1425,10 +1445,11 @@ pub struct WriteFetch<'w, T: Component> { ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, - MaybeThinSlicePtrLocation<'w>, + MaybeLocation>>>, )>, // T::STORAGE_TYPE = StorageType::SparseSet - &'w ComponentSparseSet, + // Can be `None` when the component has never been inserted + Option<&'w ComponentSparseSet>, >, last_run: Tick, this_run: Tick, @@ -1447,14 +1468,9 @@ impl Copy for WriteFetch<'_, T> {} /// `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 { - type Item<'w> = Mut<'w, T>; type Fetch<'w> = WriteFetch<'w, T>; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1474,13 +1490,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { // which we are allowed to access since we registered it in `update_archetype_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) - .debug_checked_unwrap() - } + unsafe { world.storages().sparse_sets.get(component_id) } }, ), last_run, @@ -1521,15 +1531,51 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { 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(), - #[cfg(feature = "track_location")] - column.get_changed_by_slice(table.entity_count()).into(), - #[cfg(not(feature = "track_location"))] - (), + column + .get_changed_by_slice(table.entity_count()) + .map(Into::into), )); // SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table unsafe { fetch.components.set_table(table_data) }; } + fn update_component_access( + &component_id: &ComponentId, + access: &mut FilteredAccess, + ) { + assert!( + !access.access().has_component_read(component_id), + "&mut {} conflicts with a previous access in this query. Mutable component access must be unique.", + core::any::type_name::(), + ); + access.add_component_write(component_id); + } + + fn init_state(world: &mut World) -> ComponentId { + world.register_component::() + } + + fn get_state(components: &Components) -> Option { + components.component_id::() + } + + fn matches_component_set( + &state: &ComponentId, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + set_contains_id(state) + } +} + +/// SAFETY: access of `&T` is a subset of `&mut T` +unsafe impl<'__w, T: Component> QueryData for &'__w mut T { + type ReadOnly = &'__w T; + type Item<'w> = Mut<'w, T>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { + item + } + #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, @@ -1539,7 +1585,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { fetch.components.extract( |table| { // SAFETY: set_table was previously called - let (table_components, added_ticks, changed_ticks, _callers) = + let (table_components, added_ticks, changed_ticks, callers) = unsafe { table.debug_checked_unwrap() }; // SAFETY: The caller ensures `table_row` is in range. @@ -1549,8 +1595,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { // SAFETY: The caller ensures `table_row` is in range. let changed = unsafe { changed_ticks.get(table_row.as_usize()) }; // SAFETY: The caller ensures `table_row` is in range. - #[cfg(feature = "track_location")] - let caller = unsafe { _callers.get(table_row.as_usize()) }; + let caller = callers.map(|callers| unsafe { callers.get(table_row.as_usize()) }); Mut { value: component.deref_mut(), @@ -1560,56 +1605,26 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { this_run: fetch.this_run, last_run: fetch.last_run, }, - #[cfg(feature = "track_location")] - changed_by: caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), } }, |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let (component, ticks, _caller) = - unsafe { sparse_set.get_with_ticks(entity).debug_checked_unwrap() }; + // SAFETY: The caller ensures `entity` is in range and has the component. + let (component, ticks, caller) = unsafe { + sparse_set + .debug_checked_unwrap() + .get_with_ticks(entity) + .debug_checked_unwrap() + }; Mut { value: component.assert_unique().deref_mut(), ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run), - #[cfg(feature = "track_location")] - changed_by: _caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), } }, ) } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_component_read(component_id), - "&mut {} conflicts with a previous access in this query. Mutable component access must be unique.", - core::any::type_name::(), - ); - access.add_component_write(component_id); - } - - fn init_state(world: &mut World) -> ComponentId { - world.register_component::() - } - - fn get_state(components: &Components) -> Option { - components.component_id::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: access of `&T` is a subset of `&mut T` -unsafe impl<'__w, T: Component> QueryData for &'__w mut T { - type ReadOnly = &'__w T; } /// When `Mut` is used in a query, it will be converted to `Ref` when transformed into its read-only form, providing access to change detection methods. @@ -1622,15 +1637,9 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T /// `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> { - type Item<'w> = Mut<'w, T>; type Fetch<'w> = WriteFetch<'w, T>; type State = ComponentId; - // Forwarded to `&mut T` - fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { - <&mut T as WorldQuery>::shrink(item) - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1666,18 +1675,6 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { <&mut T as WorldQuery>::set_table(fetch, state, table); } - #[inline(always)] - // Forwarded to `&mut T` - unsafe fn fetch<'w>( - // Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here. - // But it complains nowhere else in the entire trait implementation. - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Mut<'w, T> { - <&mut T as WorldQuery>::fetch(fetch, entity, table_row) - } - // NOT forwarded to `&mut T` fn update_component_access( &component_id: &ComponentId, @@ -1713,8 +1710,26 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> { } // SAFETY: access of `Ref` is a subset of `Mut` -unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> { +unsafe impl<'__w, T: Component> QueryData for Mut<'__w, T> { type ReadOnly = Ref<'__w, T>; + type Item<'w> = Mut<'w, T>; + + // Forwarded to `&mut T` + fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { + <&mut T as QueryData>::shrink(item) + } + + #[inline(always)] + // Forwarded to `&mut T` + unsafe fn fetch<'w>( + // Rust complains about lifetime bounds not matching the trait if I directly use `WriteFetch<'w, T>` right here. + // But it complains nowhere else in the entire trait implementation. + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Mut<'w, T> { + <&mut T as QueryData>::fetch(fetch, entity, table_row) + } } #[doc(hidden)] @@ -1737,14 +1752,9 @@ impl Clone for OptionFetch<'_, T> { /// This is sound because `update_component_access` and `update_archetype_component_access` add the same accesses as `T`. /// Filters are unchanged. unsafe impl WorldQuery for Option { - type Item<'w> = Option>; type Fetch<'w> = OptionFetch<'w, T>; type State = T::State; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item.map(T::shrink) - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { OptionFetch { fetch: T::shrink_fetch(fetch.fetch), @@ -1795,18 +1805,6 @@ unsafe impl WorldQuery for Option { } } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - fetch - .matches - // SAFETY: The invariants are upheld by the caller. - .then(|| unsafe { T::fetch(&mut fetch.fetch, entity, table_row) }) - } - fn update_component_access(state: &T::State, access: &mut FilteredAccess) { // FilteredAccess::add_[write,read] adds the component to the `with` filter. // Those methods are called on `access` in `T::update_component_access`. @@ -1841,6 +1839,23 @@ unsafe impl WorldQuery for Option { // SAFETY: defers to soundness of `T: WorldQuery` impl unsafe impl QueryData for Option { type ReadOnly = Option; + type Item<'w> = Option>; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item.map(T::shrink) + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + fetch + .matches + // SAFETY: The invariants are upheld by the caller. + .then(|| unsafe { T::fetch(&mut fetch.fetch, entity, table_row) }) + } } /// SAFETY: [`OptionFetch`] is read only because `T` is read only @@ -1921,14 +1936,9 @@ impl core::fmt::Debug for Has { /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for Has { - type Item<'w> = bool; type Fetch<'w> = bool; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -1965,15 +1975,6 @@ unsafe impl WorldQuery for Has { *fetch = table.has_column(*state); } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - *fetch - } - fn update_component_access( &component_id: &Self::State, access: &mut FilteredAccess, @@ -2001,6 +2002,20 @@ unsafe impl WorldQuery for Has { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for Has { type ReadOnly = Self; + type Item<'w> = bool; + + 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> { + *fetch + } } /// SAFETY: [`Has`] is read only @@ -2015,10 +2030,45 @@ pub struct AnyOf(PhantomData); macro_rules! impl_tuple_query_data { ($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] $(#[$meta])* // SAFETY: defers to soundness `$name: WorldQuery` impl unsafe impl<$($name: QueryData),*> QueryData for ($($name,)*) { type ReadOnly = ($($name::ReadOnly,)*); + type Item<'w> = ($($name::Item<'w>,)*); + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + let ($($name,)*) = item; + ($( + $name::shrink($name), + )*) + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow + ) -> Self::Item<'w> { + let ($($name,)*) = fetch; + // SAFETY: The invariants are upheld by the caller. + ($(unsafe { $name::fetch($name, entity, table_row) },)*) + } } $(#[$meta])* @@ -2054,15 +2104,8 @@ macro_rules! impl_anytuple_fetch { /// 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,)*)> { type Fetch<'w> = ($(($name::Fetch<'w>, bool),)*); - type Item<'w> = ($(Option<$name::Item<'w>>,)*); type State = ($($name::State,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - let ($($name,)*) = item; - ($( - $name.map($name::shrink), - )*) - } fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($name,)*) = fetch; ($( @@ -2073,7 +2116,7 @@ macro_rules! impl_anytuple_fetch { #[inline] unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> { let ($($name,)*) = state; - // SAFETY: The invariants are upheld by the caller. + // SAFETY: The invariants are upheld by the caller. ($(( unsafe { $name::init_fetch(_world, $name, _last_run, _this_run) }, false),)*) } @@ -2091,7 +2134,7 @@ macro_rules! impl_anytuple_fetch { $( $name.1 = $name::matches_component_set($state, &|id| _archetype.contains(id)); if $name.1 { - // SAFETY: The invariants are upheld by the caller. + // SAFETY: The invariants are upheld by the caller. unsafe { $name::set_archetype(&mut $name.0, $state, _archetype, _table); } } )* @@ -2104,25 +2147,12 @@ macro_rules! impl_anytuple_fetch { $( $name.1 = $name::matches_component_set($state, &|id| _table.has_column(id)); if $name.1 { - // SAFETY: The invariants are required to be upheld by the caller. + // SAFETY: The invariants are required to be upheld by the caller. unsafe { $name::set_table(&mut $name.0, $state, _table); } } )* } - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow - ) -> Self::Item<'w> { - let ($($name,)*) = _fetch; - ($( - // SAFETY: The invariants are required to be upheld by the caller. - $name.1.then(|| unsafe { $name::fetch(&mut $name.0, _entity, _table_row) }), - )*) - } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { // update the filters (Or<(With<$name>,)>) let ($($name,)*) = state; @@ -2162,10 +2192,47 @@ macro_rules! impl_anytuple_fetch { } } + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + #[allow( + clippy::unused_unit, + reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." + )] $(#[$meta])* // SAFETY: defers to soundness of `$name: WorldQuery` impl unsafe impl<$($name: QueryData),*> QueryData for AnyOf<($($name,)*)> { type ReadOnly = AnyOf<($($name::ReadOnly,)*)>; + type Item<'w> = ($(Option<$name::Item<'w>>,)*); + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + let ($($name,)*) = item; + ($( + $name.map($name::shrink), + )*) + } + + #[inline(always)] + unsafe fn fetch<'w>( + _fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow + ) -> Self::Item<'w> { + let ($($name,)*) = _fetch; + ($( + // SAFETY: The invariants are required to be upheld by the caller. + $name.1.then(|| unsafe { $name::fetch(&mut $name.0, _entity, _table_row) }), + )*) + } } $(#[$meta])* @@ -2200,12 +2267,9 @@ pub(crate) struct NopWorldQuery(PhantomData); /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for NopWorldQuery { - type Item<'w> = (); type Fetch<'w> = (); type State = D::State; - fn shrink<'wlong: 'wshort, 'wshort>(_: ()) {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: ()) {} #[inline(always)] @@ -2231,14 +2295,6 @@ unsafe impl WorldQuery for NopWorldQuery { #[inline(always)] unsafe fn set_table<'w>(_fetch: &mut (), _state: &D::State, _table: &Table) {} - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - fn update_component_access(_state: &D::State, _access: &mut FilteredAccess) {} fn init_state(world: &mut World) -> Self::State { @@ -2260,6 +2316,17 @@ unsafe impl WorldQuery for NopWorldQuery { /// SAFETY: `Self::ReadOnly` is `Self` unsafe impl QueryData for NopWorldQuery { type ReadOnly = Self; + type Item<'w> = (); + + fn shrink<'wlong: 'wshort, 'wshort>(_: ()) {} + + #[inline(always)] + unsafe fn fetch<'w>( + _fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + } } /// SAFETY: `NopFetch` never accesses any data @@ -2269,13 +2336,10 @@ unsafe impl ReadOnlyQueryData for NopWorldQuery {} /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. unsafe impl WorldQuery for PhantomData { - type Item<'a> = (); type Fetch<'a> = (); type State = (); - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { } @@ -2302,13 +2366,6 @@ unsafe impl WorldQuery for PhantomData { unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { } - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess) {} fn init_state(_world: &mut World) -> Self::State {} @@ -2328,6 +2385,16 @@ unsafe impl WorldQuery for PhantomData { /// SAFETY: `Self::ReadOnly` is `Self` unsafe impl QueryData for PhantomData { type ReadOnly = Self; + type Item<'a> = (); + + fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} + + unsafe fn fetch<'w>( + _fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + } } /// SAFETY: `PhantomData` never accesses any world data. @@ -2406,10 +2473,7 @@ mod tests { use bevy_ecs_macros::QueryData; use super::*; - use crate::{ - self as bevy_ecs, - system::{assert_is_system, Query}, - }; + use crate::system::{assert_is_system, Query}; #[derive(Component)] pub struct A; diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index babc15c9d64dd..9e4f36a9655f6 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -66,7 +66,6 @@ use variadics_please::all_tuples; /// # bevy_ecs::system::assert_is_system(my_system); /// ``` /// -/// [`fetch`]: Self::fetch /// [`matches_component_set`]: Self::matches_component_set /// [`Query`]: crate::system::Query /// [`State`]: Self::State @@ -97,7 +96,7 @@ pub unsafe trait QueryFilter: WorldQuery { /// ones that are compatible with the Filter's access. /// /// Implementors of this method will generally either have a trivial `true` body (required for archetypal filters), - /// or call [`WorldQuery::fetch`] to access the raw data needed to make the final decision on filter inclusion. + /// or access the necessary data within this function to make the final decision on filter inclusion. /// /// # Safety /// @@ -141,16 +140,13 @@ pub struct With(PhantomData); /// SAFETY: /// `update_component_access` does not add any accesses. -/// This is sound because `fetch` does not access any components. +/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. /// `update_component_access` adds a `With` filter for `T`. /// This is sound because `matches_component_set` returns whether the set contains the component. unsafe impl WorldQuery for With { - type Item<'w> = (); type Fetch<'w> = (); type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] @@ -181,14 +177,6 @@ unsafe impl WorldQuery for With { #[inline] unsafe fn set_table(_fetch: &mut (), _state: &ComponentId, _table: &Table) {} - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - #[inline] fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { access.and_with(id); @@ -252,16 +240,13 @@ pub struct Without(PhantomData); /// SAFETY: /// `update_component_access` does not add any accesses. -/// This is sound because `fetch` does not access any components. +/// This is sound because [`QueryFilter::filter_fetch`] does not access any components. /// `update_component_access` adds a `Without` filter for `T`. /// This is sound because `matches_component_set` returns whether the set does not contain the component. unsafe impl WorldQuery for Without { - type Item<'w> = (); type Fetch<'w> = (); type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} #[inline] @@ -292,14 +277,6 @@ unsafe impl WorldQuery for Without { #[inline] unsafe fn set_table(_fetch: &mut (), _state: &Self::State, _table: &Table) {} - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - #[inline] fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { access.and_without(id); @@ -402,19 +379,14 @@ macro_rules! impl_or_query_filter { reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case." )] /// SAFETY: - /// `fetch` accesses are a subset of the subqueries' accesses + /// [`QueryFilter::filter_fetch`] accesses are a subset of the subqueries' accesses /// This is sound because `update_component_access` adds accesses according to the implementations of all the subqueries. /// `update_component_access` replace 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<$($filter: QueryFilter),*> WorldQuery for Or<($($filter,)*)> { type Fetch<'w> = ($(OrFetch<'w, $filter>,)*); - type Item<'w> = bool; type State = ($($filter::State,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($filter,)*) = fetch; ($( @@ -468,17 +440,6 @@ macro_rules! impl_or_query_filter { )* } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow - ) -> Self::Item<'w> { - let ($($filter,)*) = fetch; - // SAFETY: The invariants are upheld by the caller. - false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, entity, table_row) }))* - } - fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($filter,)*) = state; @@ -515,6 +476,18 @@ macro_rules! impl_or_query_filter { } } + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such the lints below may not always apply." + )] + #[allow( + non_snake_case, + reason = "The names of some variables are provided by the macro's caller, not by us." + )] + #[allow( + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] $(#[$meta])* // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { @@ -526,8 +499,9 @@ macro_rules! impl_or_query_filter { entity: Entity, table_row: TableRow ) -> bool { + let ($($filter,)*) = fetch; // SAFETY: The invariants are upheld by the caller. - unsafe { Self::fetch(fetch, entity, table_row) } + false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, entity, table_row) }))* } } }; @@ -656,7 +630,8 @@ pub struct AddedFetch<'w, T: Component> { // T::STORAGE_TYPE = StorageType::Table Option>>, // T::STORAGE_TYPE = StorageType::SparseSet - &'w ComponentSparseSet, + // Can be `None` when the component has never been inserted + Option<&'w ComponentSparseSet>, >, last_run: Tick, this_run: Tick, @@ -673,19 +648,14 @@ impl Clone for AddedFetch<'_, T> { } /// SAFETY: -/// `fetch` accesses a single component in a readonly way. +/// [`QueryFilter::filter_fetch`] accesses a single component in a readonly way. /// This is sound because `update_component_access` adds read access for that component and panics 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 Added { - type Item<'w> = bool; type Fetch<'w> = AddedFetch<'w, T>; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -705,7 +675,7 @@ unsafe impl WorldQuery for Added { // which we are allowed to access since we registered it in `update_archetype_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).debug_checked_unwrap() } + unsafe { world.storages().sparse_sets.get(id) } }, ), last_run, @@ -751,32 +721,6 @@ unsafe impl WorldQuery for Added { unsafe { fetch.ticks.set_table(table_ticks) }; } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - fetch.ticks.extract( - |table| { - // 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()) }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let tick = unsafe { - ComponentSparseSet::get_added_tick(sparse_set, entity).debug_checked_unwrap() - }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - ) - } - #[inline] fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { if access.access().has_component_write(id) { @@ -811,7 +755,27 @@ unsafe impl QueryFilter for Added { table_row: TableRow, ) -> bool { // SAFETY: The invariants are upheld by the caller. - unsafe { Self::fetch(fetch, entity, table_row) } + fetch.ticks.extract( + |table| { + // 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()) }; + + tick.deref().is_newer_than(fetch.last_run, fetch.this_run) + }, + |sparse_set| { + // SAFETY: The caller ensures `entity` is in range. + let tick = unsafe { + sparse_set + .debug_checked_unwrap() + .get_added_tick(entity) + .debug_checked_unwrap() + }; + + tick.deref().is_newer_than(fetch.last_run, fetch.this_run) + }, + ) } } @@ -890,7 +854,12 @@ pub struct Changed(PhantomData); #[doc(hidden)] pub struct ChangedFetch<'w, T: Component> { - ticks: StorageSwitch>>, &'w ComponentSparseSet>, + ticks: StorageSwitch< + T, + Option>>, + // Can be `None` when the component has never been inserted + Option<&'w ComponentSparseSet>, + >, last_run: Tick, this_run: Tick, } @@ -911,14 +880,9 @@ impl Clone for ChangedFetch<'_, T> { /// `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 Changed { - type Item<'w> = bool; type Fetch<'w> = ChangedFetch<'w, T>; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { fetch } @@ -938,7 +902,7 @@ unsafe impl WorldQuery for Changed { // which we are allowed to access since we registered it in `update_archetype_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).debug_checked_unwrap() } + unsafe { world.storages().sparse_sets.get(id) } }, ), last_run, @@ -984,32 +948,6 @@ unsafe impl WorldQuery for Changed { unsafe { fetch.ticks.set_table(table_ticks) }; } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - fetch.ticks.extract( - |table| { - // 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()) }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - |sparse_set| { - // SAFETY: The caller ensures `entity` is in range. - let tick = unsafe { - ComponentSparseSet::get_changed_tick(sparse_set, entity).debug_checked_unwrap() - }; - - tick.deref().is_newer_than(fetch.last_run, fetch.this_run) - }, - ) - } - #[inline] fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) { if access.access().has_component_write(id) { @@ -1045,7 +983,27 @@ unsafe impl QueryFilter for Changed { table_row: TableRow, ) -> bool { // SAFETY: The invariants are upheld by the caller. - unsafe { Self::fetch(fetch, entity, table_row) } + fetch.ticks.extract( + |table| { + // 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()) }; + + tick.deref().is_newer_than(fetch.last_run, fetch.this_run) + }, + |sparse_set| { + // SAFETY: The caller ensures `entity` is in range. + let tick = unsafe { + sparse_set + .debug_checked_unwrap() + .get_changed_tick(entity) + .debug_checked_unwrap() + }; + + tick.deref().is_newer_than(fetch.last_run, fetch.this_run) + }, + ) } } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 39b30628912ab..d81ce7b85928f 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -849,13 +849,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { // SAFETY: // `self.world` has permission to access the required components. // The original query iter has not been iterated on, so no items are aliased from it. - let query_lens = unsafe { - query_lens_state.iter_unchecked_manual( - world, - world.last_change_tick(), - world.change_tick(), - ) - }; + // `QueryIter::new` ensures `world` is the same one used to initialize `query_state`. + let query_lens = unsafe { query_lens_state.query_unchecked_manual(world) }.into_iter(); let mut keyed_query: Vec<_> = query_lens .map(|(key, entity)| (key, NeutralOrd(entity))) .collect(); @@ -1315,7 +1310,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> /// // We need to collect the internal iterator before iterating mutably /// let mut parent_query_iter = query.iter_many_mut(entity_list) /// .sort::(); - /// + /// /// let mut scratch_value = 0; /// while let Some(mut part_value) = parent_query_iter.fetch_next_back() /// { @@ -1689,14 +1684,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> // SAFETY: // `self.world` has permission to access the required components. // The original query iter has not been iterated on, so no items are aliased from it. - let query_lens = unsafe { - query_lens_state.iter_many_unchecked_manual( - self.entity_iter, - world, - world.last_change_tick(), - world.change_tick(), - ) - }; + // `QueryIter::new` ensures `world` is the same one used to initialize `query_state`. + let query_lens = unsafe { query_lens_state.query_unchecked_manual(world) } + .iter_many_inner(self.entity_iter); let mut keyed_query: Vec<_> = query_lens .map(|(key, entity)| (key, NeutralOrd(entity))) .collect(); @@ -2445,7 +2435,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { } // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: - // QueryIter, QueryIterationCursor, QuerySortedIter, QueryManyIter, QuerySortedManyIter, QueryCombinationIter, QueryState::par_fold_init_unchecked_manual + // QueryIter, QueryIterationCursor, QuerySortedIter, QueryManyIter, QuerySortedManyIter, QueryCombinationIter, + // QueryState::par_fold_init_unchecked_manual, QueryState::par_many_fold_init_unchecked_manual, + // QueryState::par_many_unique_fold_init_unchecked_manual /// # Safety /// `tables` and `archetypes` must belong to the same world that the [`QueryIterationCursor`] /// was initialized for. @@ -2594,7 +2586,6 @@ mod tests { use crate::component::Component; use crate::entity::Entity; use crate::prelude::World; - use crate::{self as bevy_ecs}; #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] struct A(f32); diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 6104d0a543abb..7f304b7b712b5 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -104,7 +104,6 @@ impl DebugCheckedUnwrap for Option { #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, archetype::Archetype, component::{Component, ComponentId, Components, Tick}, prelude::{AnyOf, Changed, Entity, Or, QueryState, Res, ResMut, Resource, With, Without}, @@ -438,6 +437,18 @@ mod tests { ); } + #[test] + fn get_many_only_mut_checks_duplicates() { + let mut world = World::new(); + let id = world.spawn(A(10)).id(); + let mut query_state = world.query::<&mut A>(); + let mut query = query_state.query_mut(&mut world); + let result = query.get_many([id, id]); + assert_eq!(result, Ok([&A(10), &A(10)])); + let mut_result = query.get_many_mut([id, id]); + assert!(mut_result.is_err()); + } + #[test] fn multi_storage_query() { let mut world = World::new(); @@ -809,12 +820,9 @@ mod tests { /// `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 Item<'w> = (); type Fetch<'w> = (); type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} - fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {} unsafe fn init_fetch<'w>( @@ -844,14 +852,6 @@ mod tests { ) { } - #[inline(always)] - unsafe fn fetch<'w>( - _fetch: &mut Self::Fetch<'w>, - _entity: Entity, - _table_row: TableRow, - ) -> Self::Item<'w> { - } - fn update_component_access( &component_id: &Self::State, access: &mut FilteredAccess, @@ -882,6 +882,17 @@ mod tests { /// SAFETY: `Self` is the same as `Self::ReadOnly` unsafe impl QueryData for ReadsRData { type ReadOnly = Self; + type Item<'w> = (); + + fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {} + + #[inline(always)] + unsafe fn fetch<'w>( + _fetch: &mut Self::Fetch<'w>, + _entity: Entity, + _table_row: TableRow, + ) -> Self::Item<'w> { + } } /// SAFETY: access is read only diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index b3ea93fbbc514..0805774f8cdde 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,8 +1,13 @@ use crate::{ - batching::BatchingStrategy, component::Tick, world::unsafe_world_cell::UnsafeWorldCell, + batching::BatchingStrategy, + component::Tick, + entity::{EntityBorrow, TrustedEntityBorrow, UniqueEntityVec}, + world::unsafe_world_cell::UnsafeWorldCell, }; -use super::{QueryData, QueryFilter, QueryItem, QueryState}; +use super::{QueryData, QueryFilter, QueryItem, QueryState, ReadOnlyQueryData}; + +use alloc::vec::Vec; /// A parallel iterator over query results of a [`Query`](crate::system::Query). /// @@ -54,7 +59,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { /// fn system(query: Query<&T>){ /// let mut queue: Parallel = Parallel::default(); /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; - /// query.par_iter().for_each_init(|| queue.borrow_local_mut(),|local_queue,item| { + /// query.par_iter().for_each_init(|| queue.borrow_local_mut(),|local_queue, item| { /// **local_queue += 1; /// }); /// @@ -89,7 +94,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { // at the same time. unsafe { self.state - .iter_unchecked_manual(self.world, self.last_run, self.this_run) + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .into_iter() .fold(init, func); } } @@ -101,7 +107,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { // SAFETY: See the safety comment above. unsafe { self.state - .iter_unchecked_manual(self.world, self.last_run, self.this_run) + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .into_iter() .fold(init, func); } } else { @@ -146,3 +153,316 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> { .calc_batch_size(max_items, thread_count) } } + +/// A parallel iterator over the unique query items generated from an [`Entity`] list. +/// +/// This struct is created by the [`Query::par_iter_many`] method. +/// +/// [`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(crate) world: UnsafeWorldCell<'w>, + pub(crate) state: &'s QueryState, + pub(crate) entity_list: Vec, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, + pub(crate) batching_strategy: BatchingStrategy, +} + +impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityBorrow + Sync> + QueryParManyIter<'w, 's, D, F, E> +{ + /// Changes the batching strategy used when iterating. + /// + /// For more information on how this affects the resultant iteration, see + /// [`BatchingStrategy`]. + pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { + self.batching_strategy = strategy; + self + } + + /// Runs `func` on each query result in parallel. + /// + /// # Panics + /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[inline] + pub fn for_each) + Send + Sync + Clone>(self, func: FN) { + self.for_each_init(|| {}, |_, item| func(item)); + } + + /// Runs `func` on each query result in parallel on a value returned by `init`. + /// + /// `init` may be called multiple times per thread, and the values returned may be discarded between tasks on any given thread. + /// Callers should avoid using this function as if it were a parallel version + /// of [`Iterator::fold`]. + /// + /// # Example + /// + /// ``` + /// 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; + /// # fn some_expensive_operation(_item: &T) -> usize { + /// # 0 + /// # } + /// + /// #[derive(Component)] + /// struct T; + /// + /// #[derive(Resource)] + /// struct V(Vec); + /// + /// impl<'a> IntoIterator for &'a V { + /// // ... + /// # type Item = &'a Entity; + /// # type IntoIter = slice::Iter<'a, Entity>; + /// # + /// # fn into_iter(self) -> Self::IntoIter { + /// # self.0.iter() + /// # } + /// } + /// + /// fn system(query: Query<&T>, entities: Res){ + /// let mut queue: Parallel = Parallel::default(); + /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; + /// 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(); + /// } + /// ``` + /// + /// # Panics + /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[inline] + pub fn for_each_init(self, init: INIT, func: FN) + where + FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone, + INIT: Fn() -> T + Sync + Send + Clone, + { + let func = |mut init, item| { + func(&mut init, item); + init + }; + #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] + { + let init = init(); + // SAFETY: + // This method can only be called once per instance of QueryParManyIter, + // which ensures that mutable queries cannot be executed multiple times at once. + // Mutable instances of QueryParManyUniqueIter can only be created via an exclusive borrow of a + // Query or a World, which ensures that multiple aliasing QueryParManyIters cannot exist + // at the same time. + unsafe { + self.state + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .iter_many_inner(&self.entity_list) + .fold(init, func); + } + } + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + { + let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); + if thread_count <= 1 { + let init = init(); + // SAFETY: See the safety comment above. + unsafe { + self.state + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .iter_many_inner(&self.entity_list) + .fold(init, func); + } + } else { + // Need a batch size of at least 1. + let batch_size = self.get_batch_size(thread_count).max(1); + // SAFETY: See the safety comment above. + unsafe { + self.state.par_many_fold_init_unchecked_manual( + init, + self.world, + &self.entity_list, + batch_size, + func, + self.last_run, + self.this_run, + ); + } + } + } + } + + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + fn get_batch_size(&self, thread_count: usize) -> usize { + self.batching_strategy + .calc_batch_size(|| self.entity_list.len(), thread_count) + } +} + +/// A parallel iterator over the unique query items generated from an [`EntitySet`]. +/// +/// This struct is created by the [`Query::par_iter_many_unique`] and [`Query::par_iter_many_unique_mut`] methods. +/// +/// [`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(crate) world: UnsafeWorldCell<'w>, + pub(crate) state: &'s QueryState, + pub(crate) entity_list: UniqueEntityVec, + 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> + QueryParManyUniqueIter<'w, 's, D, F, E> +{ + /// Changes the batching strategy used when iterating. + /// + /// For more information on how this affects the resultant iteration, see + /// [`BatchingStrategy`]. + pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self { + self.batching_strategy = strategy; + self + } + + /// Runs `func` on each query result in parallel. + /// + /// # Panics + /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[inline] + pub fn for_each) + Send + Sync + Clone>(self, func: FN) { + self.for_each_init(|| {}, |_, item| func(item)); + } + + /// Runs `func` on each query result in parallel on a value returned by `init`. + /// + /// `init` may be called multiple times per thread, and the values returned may be discarded between tasks on any given thread. + /// Callers should avoid using this function as if it were a parallel version + /// of [`Iterator::fold`]. + /// + /// # Example + /// + /// ``` + /// use bevy_utils::Parallel; + /// 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 { + /// # 0 + /// # } + /// + /// #[derive(Component)] + /// struct T; + /// + /// #[derive(Resource)] + /// struct V(UniqueEntityVec); + /// + /// impl<'a> IntoIterator for &'a V { + /// // ... + /// # type Item = &'a Entity; + /// # type IntoIter = UniqueEntityIter>; + /// # + /// # fn into_iter(self) -> Self::IntoIter { + /// # self.0.iter() + /// # } + /// } + /// + /// fn system(query: Query<&T>, entities: Res){ + /// let mut queue: Parallel = Parallel::default(); + /// // queue.borrow_local_mut() will get or create a thread_local queue for each task/thread; + /// 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(); + /// } + /// ``` + /// + /// # Panics + /// If the [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[inline] + pub fn for_each_init(self, init: INIT, func: FN) + where + FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone, + INIT: Fn() -> T + Sync + Send + Clone, + { + let func = |mut init, item| { + func(&mut init, item); + init + }; + #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] + { + let init = init(); + // SAFETY: + // This method can only be called once per instance of QueryParManyUniqueIter, + // which ensures that mutable queries cannot be executed multiple times at once. + // Mutable instances of QueryParManyUniqueIter can only be created via an exclusive borrow of a + // Query or a World, which ensures that multiple aliasing QueryParManyUniqueIters cannot exist + // at the same time. + unsafe { + self.state + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .iter_many_unique_inner(self.entity_list) + .fold(init, func); + } + } + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + { + let thread_count = bevy_tasks::ComputeTaskPool::get().thread_num(); + if thread_count <= 1 { + let init = init(); + // SAFETY: See the safety comment above. + unsafe { + self.state + .query_unchecked_manual_with_ticks(self.world, self.last_run, self.this_run) + .iter_many_unique_inner(self.entity_list) + .fold(init, func); + } + } else { + // Need a batch size of at least 1. + let batch_size = self.get_batch_size(thread_count).max(1); + // SAFETY: See the safety comment above. + unsafe { + self.state.par_many_unique_fold_init_unchecked_manual( + init, + self.world, + &self.entity_list, + batch_size, + func, + self.last_run, + self.this_run, + ); + } + } + } + } + + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + fn get_batch_size(&self, thread_count: usize) -> usize { + self.batching_strategy + .calc_batch_size(|| self.entity_list.len(), thread_count) + } +} diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e0e0856fd2369..1e851285dcb03 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,20 +1,20 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - batching::BatchingStrategy, component::{ComponentId, Tick}, entity::{Entity, EntityBorrow, EntitySet}, entity_disabling::DefaultQueryFilters, prelude::FromWorld, - query::{ - Access, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, - WorldQuery, - }, + query::{Access, 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::{TrustedEntityBorrow, UniqueEntitySlice}; + use alloc::vec::Vec; -use core::{fmt, mem::MaybeUninit, ptr}; +use core::{fmt, ptr}; use fixedbitset::FixedBitSet; use log::warn; #[cfg(feature = "trace")] @@ -22,7 +22,7 @@ use tracing::Span; use super::{ NopWorldQuery, QueryBuilder, QueryData, QueryEntityError, QueryFilter, QueryManyIter, - QueryManyUniqueIter, QuerySingleError, ROQueryItem, + QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, }; /// An ID for either a table or an archetype. Used for Query iteration. @@ -154,9 +154,7 @@ impl QueryState { pub fn matched_archetypes(&self) -> impl Iterator + '_ { self.matched_archetypes.ones().map(ArchetypeId::new) } -} -impl QueryState { /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. pub fn new(world: &mut World) -> Self { let mut state = Self::new_uninitialized(world); @@ -259,7 +257,7 @@ impl QueryState { let mut is_dense = D::IS_DENSE && F::IS_DENSE; if let Some(default_filters) = world.get_resource::() { - default_filters.apply(&mut component_access); + default_filters.modify_access(&mut component_access); is_dense &= default_filters.is_dense(world.components()); } @@ -294,7 +292,7 @@ impl QueryState { let mut is_dense = builder.is_dense(); if let Some(default_filters) = builder.world().get_resource::() { - default_filters.apply(&mut component_access); + default_filters.modify_access(&mut component_access); is_dense &= default_filters.is_dense(builder.world().components()); } @@ -319,6 +317,134 @@ impl QueryState { state } + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. + pub fn query<'w, 's>(&'s mut self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { + self.update_archetypes(world); + self.query_manual(world) + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// This method is slightly more efficient than [`QueryState::query`] in some situations, since + /// it does not update this instance's internal cache. The resulting query may skip an entity that + /// belongs to an archetype that has not been cached. + /// + /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. + /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable + /// access to `self`. + /// + /// This will create read-only queries, see [`Self::query_mut`] for mutable queries. + pub fn query_manual<'w, 's>(&'s self, world: &'w World) -> Query<'w, 's, D::ReadOnly, F> { + self.validate_world(world.id()); + // SAFETY: + // - We have read access to the entire world, and we call `as_readonly()` so the query only performs read access. + // - We called `validate_world`. + unsafe { + self.as_readonly() + .query_unchecked_manual(world.as_unsafe_world_cell_readonly()) + } + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + pub fn query_mut<'w, 's>(&'s mut self, world: &'w mut World) -> Query<'w, 's, D, F> { + let last_run = world.last_change_tick(); + let this_run = world.change_tick(); + // SAFETY: We have exclusive access to the entire world. + unsafe { self.query_unchecked_with_ticks(world.as_unsafe_world_cell(), last_run, this_run) } + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + pub unsafe fn query_unchecked<'w, 's>( + &'s mut self, + world: UnsafeWorldCell<'w>, + ) -> Query<'w, 's, D, F> { + self.update_archetypes_unsafe_world_cell(world); + // SAFETY: Caller ensures we have the required access + unsafe { self.query_unchecked_manual(world) } + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// This method is slightly more efficient than [`QueryState::query_unchecked`] in some situations, since + /// it does not update this instance's internal cache. The resulting query may skip an entity that + /// belongs to an archetype that has not been cached. + /// + /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. + /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable + /// access to `self`. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` + /// with a mismatched [`WorldId`] is unsound. + pub unsafe fn query_unchecked_manual<'w, 's>( + &'s self, + world: UnsafeWorldCell<'w>, + ) -> Query<'w, 's, D, F> { + let last_run = world.last_change_tick(); + let this_run = world.change_tick(); + // SAFETY: + // - The caller ensured we have the correct access to the world. + // - The caller ensured that the world matches. + unsafe { self.query_unchecked_manual_with_ticks(world, last_run, this_run) } + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + pub unsafe fn query_unchecked_with_ticks<'w, 's>( + &'s mut self, + world: UnsafeWorldCell<'w>, + last_run: Tick, + this_run: Tick, + ) -> Query<'w, 's, D, F> { + self.update_archetypes_unsafe_world_cell(world); + // SAFETY: + // - The caller ensured we have the correct access to the world. + // - We called `update_archetypes_unsafe_world_cell`, which calls `validate_world`. + unsafe { self.query_unchecked_manual_with_ticks(world, last_run, this_run) } + } + + /// Creates a [`Query`] from the given [`QueryState`] and [`World`]. + /// + /// This method is slightly more efficient than [`QueryState::query_unchecked_with_ticks`] in some situations, since + /// it does not update this instance's internal cache. The resulting query may skip an entity that + /// belongs to an archetype that has not been cached. + /// + /// To ensure that the cache is up to date, call [`QueryState::update_archetypes`] before this method. + /// The cache is also updated in [`QueryState::new`], [`QueryState::get`], or any method with mutable + /// access to `self`. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` + /// with a mismatched [`WorldId`] is unsound. + pub unsafe fn query_unchecked_manual_with_ticks<'w, 's>( + &'s self, + world: UnsafeWorldCell<'w>, + last_run: Tick, + this_run: Tick, + ) -> Query<'w, 's, D, F> { + // SAFETY: + // - The caller ensured we have the correct access to the world. + // - The caller ensured that the world matches. + unsafe { Query::new(world, self, last_run, this_run) } + } + /// Checks if the query is empty for the given [`World`], where the last change and current tick are given. /// /// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)` @@ -336,15 +462,16 @@ impl QueryState { pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool { self.validate_world(world.id()); // SAFETY: - // - We have read-only access to the entire world. - // - The world has been validated. + // - We have read access to the entire world, and `is_empty()` only performs read access. + // - We called `validate_world`. unsafe { - self.is_empty_unsafe_world_cell( + self.query_unchecked_manual_with_ticks( world.as_unsafe_world_cell_readonly(), last_run, this_run, ) } + .is_empty() } /// Returns `true` if the given [`Entity`] matches the query. @@ -352,41 +479,18 @@ impl QueryState { /// This is always guaranteed to run in `O(1)` time. #[inline] pub fn contains(&self, entity: Entity, world: &World, last_run: Tick, this_run: Tick) -> bool { - // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access - unsafe { - self.as_nop() - .get_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - entity, - last_run, - this_run, - ) - .is_ok() - } - } - - /// Checks if the query is empty for the given [`UnsafeWorldCell`]. - /// - /// # Safety - /// - /// - `world` must have permission to read any components required by this instance's `F` [`QueryFilter`]. - /// - `world` must match the one used to create this [`QueryState`]. - #[inline] - pub(crate) unsafe fn is_empty_unsafe_world_cell( - &self, - world: UnsafeWorldCell, - last_run: Tick, - this_run: Tick, - ) -> bool { + self.validate_world(world.id()); // SAFETY: - // - The caller ensures that `world` has permission to access any data used by the filter. - // - The caller ensures that the world matches. + // - We have read access to the entire world, and `is_empty()` only performs read access. + // - We called `validate_world`. unsafe { - self.as_nop() - .iter_unchecked_manual(world, last_run, this_run) - .next() - .is_none() + self.query_unchecked_manual_with_ticks( + world.as_unsafe_world_cell_readonly(), + last_run, + this_run, + ) } + .contains(entity) } /// Updates the state's internal view of the [`World`]'s archetypes. If this is not called before querying data, @@ -624,7 +728,7 @@ impl QueryState { /// You should not call [`update_archetypes`](Self::update_archetypes) on the returned [`QueryState`] as the result will be unpredictable. /// You might end up with a mix of archetypes that only matched the original query + archetypes that only match /// the new [`QueryState`]. Most of the safe methods on [`QueryState`] call [`QueryState::update_archetypes`] internally, so this - /// best used through a [`Query`](crate::system::Query). + /// best used through a [`Query`] pub fn transmute<'a, NewD: QueryData>( &self, world: impl Into>, @@ -812,16 +916,7 @@ impl QueryState { world: &'w World, entity: Entity, ) -> Result, QueryEntityError<'w>> { - self.update_archetypes(world); - // SAFETY: query is read only - unsafe { - self.as_readonly().get_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - entity, - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).get_inner(entity) } /// Returns the read-only query results for the given array of [`Entity`]. @@ -854,7 +949,7 @@ impl QueryState { /// /// let wrong_entity = Entity::from_raw(365); /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// ``` #[inline] pub fn get_many<'w, const N: usize>( @@ -862,19 +957,7 @@ impl QueryState { world: &'w World, entities: [Entity; N], ) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError<'w>> { - self.update_archetypes(world); - - // SAFETY: - // - We have read-only access to the entire world. - // - `update_archetypes` validates that the `World` matches. - unsafe { - self.get_many_read_only_manual( - world.as_unsafe_world_cell_readonly(), - entities, - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).get_many_inner(entities) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -886,18 +969,7 @@ impl QueryState { world: &'w mut World, entity: Entity, ) -> Result, QueryEntityError<'w>> { - self.update_archetypes(world); - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: query has unique world access - unsafe { - self.get_unchecked_manual( - world.as_unsafe_world_cell(), - entity, - last_change_tick, - change_tick, - ) - } + self.query_mut(world).get_inner(entity) } /// Returns the query results for the given array of [`Entity`]. @@ -934,7 +1006,7 @@ impl QueryState { /// let wrong_entity = Entity::from_raw(57); /// let invalid_entity = world.spawn_empty().id(); /// - /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity); + /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); /// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` @@ -944,20 +1016,7 @@ impl QueryState { world: &'w mut World, entities: [Entity; N], ) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { - self.update_archetypes(world); - - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: method requires exclusive world access - // and world has been validated via update_archetypes - unsafe { - self.get_many_unchecked_manual( - world.as_unsafe_world_cell(), - entities, - last_change_tick, - change_tick, - ) - } + self.query_mut(world).get_many_inner(entities) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -979,16 +1038,7 @@ impl QueryState { world: &'w World, entity: Entity, ) -> Result, QueryEntityError<'w>> { - self.validate_world(world.id()); - // SAFETY: query is read only and world is validated - unsafe { - self.as_readonly().get_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - entity, - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query_manual(world).get_inner(entity) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -1005,132 +1055,7 @@ impl QueryState { world: UnsafeWorldCell<'w>, entity: Entity, ) -> Result, QueryEntityError<'w>> { - self.update_archetypes_unsafe_world_cell(world); - self.get_unchecked_manual(world, entity, world.last_change_tick(), world.change_tick()) - } - - /// Gets the query result for the given [`World`] and [`Entity`], where the last change and - /// the current change tick are given. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// - /// This must be called on the same `World` that the `Query` was generated from: - /// use `QueryState::validate_world` to verify this. - pub(crate) unsafe fn get_unchecked_manual<'w>( - &self, - world: UnsafeWorldCell<'w>, - entity: Entity, - last_run: Tick, - this_run: Tick, - ) -> Result, QueryEntityError<'w>> { - let location = world - .entities() - .get(entity) - .ok_or(QueryEntityError::NoSuchEntity( - entity, - world.entities().entity_does_not_exist_error_details(entity), - ))?; - if !self - .matched_archetypes - .contains(location.archetype_id.index()) - { - return Err(QueryEntityError::QueryDoesNotMatch(entity, world)); - } - let archetype = world - .archetypes() - .get(location.archetype_id) - .debug_checked_unwrap(); - let mut fetch = D::init_fetch(world, &self.fetch_state, last_run, this_run); - let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run); - - let table = world - .storages() - .tables - .get(location.table_id) - .debug_checked_unwrap(); - D::set_archetype(&mut fetch, &self.fetch_state, archetype, table); - F::set_archetype(&mut filter, &self.filter_state, archetype, table); - - if F::filter_fetch(&mut filter, entity, location.table_row) { - Ok(D::fetch(&mut fetch, entity, location.table_row)) - } else { - Err(QueryEntityError::QueryDoesNotMatch(entity, world)) - } - } - - /// Gets the read-only query results for the given [`World`] and array of [`Entity`], where the last change and - /// the current change tick are given. - /// - /// # Safety - /// - /// * `world` must have permission to read all of the components returned from this call. - /// No mutable references may coexist with any of the returned references. - /// * This must be called on the same `World` that the `Query` was generated from: - /// use `QueryState::validate_world` to verify this. - pub(crate) unsafe fn get_many_read_only_manual<'w, const N: usize>( - &self, - world: UnsafeWorldCell<'w>, - entities: [Entity; N], - last_run: Tick, - this_run: Tick, - ) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError<'w>> { - let mut values = [(); N].map(|_| MaybeUninit::uninit()); - - for (value, entity) in core::iter::zip(&mut values, entities) { - // SAFETY: fetch is read-only and world must be validated - let item = unsafe { - self.as_readonly() - .get_unchecked_manual(world, entity, last_run, this_run)? - }; - *value = MaybeUninit::new(item); - } - - // SAFETY: Each value has been fully initialized. - Ok(values.map(|x| unsafe { x.assume_init() })) - } - - /// Gets the query results for the given [`World`] and array of [`Entity`], where the last change and - /// the current change tick are given. - /// - /// This is always guaranteed to run in `O(1)` time. - /// - /// # Safety - /// - /// This does not check for unique access to subsets of the entity-component data. - /// To be safe, make sure mutable queries have unique access to the components they query. - /// - /// This must be called on the same `World` that the `Query` was generated from: - /// use `QueryState::validate_world` to verify this. - pub(crate) unsafe fn get_many_unchecked_manual<'w, const N: usize>( - &self, - world: UnsafeWorldCell<'w>, - entities: [Entity; N], - last_run: Tick, - this_run: Tick, - ) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { - // Verify that all entities are unique - for i in 0..N { - for j in 0..i { - if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); - } - } - } - - let mut values = [(); N].map(|_| MaybeUninit::uninit()); - - for (value, entity) in core::iter::zip(&mut values, entities) { - let item = self.get_unchecked_manual(world, entity, last_run, this_run)?; - *value = MaybeUninit::new(item); - } - - // SAFETY: Each value has been fully initialized. - Ok(values.map(|x| x.assume_init())) + self.query_unchecked(world).get_inner(entity) } /// Returns an [`Iterator`] over the query results for the given [`World`]. @@ -1138,15 +1063,7 @@ impl QueryState { /// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries. #[inline] pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { - self.update_archetypes(world); - // SAFETY: query is read only - unsafe { - self.as_readonly().iter_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).into_iter() } /// Returns an [`Iterator`] over the query results for the given [`World`]. @@ -1155,13 +1072,7 @@ impl QueryState { /// Iteration order is not guaranteed. #[inline] pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, D, F> { - self.update_archetypes(world); - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: query has unique world access - unsafe { - self.iter_unchecked_manual(world.as_unsafe_world_cell(), last_change_tick, change_tick) - } + self.query_mut(world).into_iter() } /// Returns an [`Iterator`] over the query results for the given [`World`] without updating the query's archetypes. @@ -1173,15 +1084,7 @@ impl QueryState { /// This can only be called for read-only queries. #[inline] pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> { - self.validate_world(world.id()); - // SAFETY: query is read only and world is validated - unsafe { - self.as_readonly().iter_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query_manual(world).into_iter() } /// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition. @@ -1213,15 +1116,7 @@ impl QueryState { &'s mut self, world: &'w World, ) -> QueryCombinationIter<'w, 's, D::ReadOnly, F, K> { - self.update_archetypes(world); - // SAFETY: query is read only - unsafe { - self.as_readonly().iter_combinations_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).iter_combinations_inner() } /// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition. @@ -1246,17 +1141,7 @@ impl QueryState { &'s mut self, world: &'w mut World, ) -> QueryCombinationIter<'w, 's, D, F, K> { - self.update_archetypes(world); - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: query has unique world access - unsafe { - self.iter_combinations_unchecked_manual( - world.as_unsafe_world_cell(), - last_change_tick, - change_tick, - ) - } + self.query_mut(world).iter_combinations_inner() } /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. @@ -1273,16 +1158,7 @@ impl QueryState { world: &'w World, entities: EntityList, ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.update_archetypes(world); - // SAFETY: query is read only - unsafe { - self.as_readonly().iter_many_unchecked_manual( - entities, - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).iter_many_inner(entities) } /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. @@ -1305,16 +1181,7 @@ impl QueryState { world: &'w World, entities: EntityList, ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.validate_world(world.id()); - // SAFETY: query is read only, world id is validated - unsafe { - self.as_readonly().iter_many_unchecked_manual( - entities, - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query_manual(world).iter_many_inner(entities) } /// Returns an iterator over the query items generated from an [`Entity`] list. @@ -1327,18 +1194,7 @@ impl QueryState { world: &'w mut World, entities: EntityList, ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { - self.update_archetypes(world); - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: Query has unique world access. - unsafe { - self.iter_many_unchecked_manual( - entities, - world.as_unsafe_world_cell(), - last_change_tick, - change_tick, - ) - } + self.query_mut(world).iter_many_inner(entities) } /// Returns an [`Iterator`] over the unique read-only query items generated from an [`EntitySet`]. @@ -1355,16 +1211,7 @@ impl QueryState { world: &'w World, entities: EntityList, ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.update_archetypes(world); - // SAFETY: query is read only - unsafe { - self.as_readonly().iter_many_unique_unchecked_manual( - entities, - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).iter_many_unique_inner(entities) } /// Returns an [`Iterator`] over the unique read-only query items generated from an [`EntitySet`]. @@ -1388,16 +1235,7 @@ impl QueryState { world: &'w World, entities: EntityList, ) -> QueryManyUniqueIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { - self.validate_world(world.id()); - // SAFETY: query is read only, world id is validated - unsafe { - self.as_readonly().iter_many_unique_unchecked_manual( - entities, - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query_manual(world).iter_many_unique_inner(entities) } /// Returns an iterator over the unique query items generated from an [`EntitySet`]. @@ -1410,18 +1248,7 @@ impl QueryState { world: &'w mut World, entities: EntityList, ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { - self.update_archetypes(world); - let last_change_tick = world.last_change_tick(); - let change_tick = world.change_tick(); - // SAFETY: Query has unique world access. - unsafe { - self.iter_many_unique_unchecked_manual( - entities, - world.as_unsafe_world_cell(), - last_change_tick, - change_tick, - ) - } + self.query_mut(world).iter_many_unique_inner(entities) } /// Returns an [`Iterator`] over the query results for the given [`World`]. /// @@ -1437,8 +1264,7 @@ impl QueryState { &'s mut self, world: UnsafeWorldCell<'w>, ) -> QueryIter<'w, 's, D, F> { - self.update_archetypes_unsafe_world_cell(world); - self.iter_unchecked_manual(world, world.last_change_tick(), world.change_tick()) + self.query_unchecked(world).into_iter() } /// Returns an [`Iterator`] over all possible combinations of `K` query results for the @@ -1457,107 +1283,7 @@ impl QueryState { &'s mut self, world: UnsafeWorldCell<'w>, ) -> QueryCombinationIter<'w, 's, D, F, K> { - self.update_archetypes_unsafe_world_cell(world); - self.iter_combinations_unchecked_manual( - world, - world.last_change_tick(), - world.change_tick(), - ) - } - - /// Returns an [`Iterator`] for the given [`World`], where the last change and - /// the current change tick are given. - /// - /// This iterator is always guaranteed to return results from each matching entity once and only once. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - #[inline] - pub(crate) unsafe fn iter_unchecked_manual<'w, 's>( - &'s self, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> QueryIter<'w, 's, D, F> { - QueryIter::new(world, self, last_run, this_run) - } - - /// Returns an [`Iterator`] for the given [`World`] and list of [`Entity`]'s, where the last change and - /// the current change tick are given. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not check for entity uniqueness - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - #[inline] - pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList>( - &'s self, - entities: EntityList, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> - where - EntityList: IntoIterator, - { - QueryManyIter::new(world, self, entities, last_run, this_run) - } - - /// Returns an [`Iterator`] for the given [`World`] and an [`EntitySet`], where the last change and - /// the current change tick are given. - /// - /// Items are returned in the order of the list of entities. - /// Entities that don't match the query are skipped. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - #[inline] - pub(crate) unsafe fn iter_many_unique_unchecked_manual<'w, 's, EntityList: EntitySet>( - &'s self, - entities: EntityList, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { - QueryManyUniqueIter::new(world, self, entities, last_run, this_run) - } - - /// Returns an [`Iterator`] over all possible combinations of `K` query results for the - /// given [`World`] without repetition. - /// This can only be called for read-only queries. - /// - /// This iterator is always guaranteed to return results from each unique pair of matching entities. - /// Iteration order is not guaranteed. - /// - /// # Safety - /// - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. - /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` - /// with a mismatched [`WorldId`] is unsound. - #[inline] - pub(crate) unsafe fn iter_combinations_unchecked_manual<'w, 's, const K: usize>( - &'s self, - world: UnsafeWorldCell<'w>, - last_run: Tick, - this_run: Tick, - ) -> QueryCombinationIter<'w, 's, D, F, K> { - QueryCombinationIter::new(world, self, last_run, this_run) + self.query_unchecked(world).iter_combinations_inner() } /// Returns a parallel iterator over the query results for the given [`World`]. @@ -1573,14 +1299,7 @@ impl QueryState { &'s mut self, world: &'w World, ) -> QueryParIter<'w, 's, D::ReadOnly, F> { - self.update_archetypes(world); - QueryParIter { - world: world.as_unsafe_world_cell_readonly(), - state: self.as_readonly(), - last_run: world.last_change_tick(), - this_run: world.read_change_tick(), - batching_strategy: BatchingStrategy::new(), - } + self.query(world).par_iter_inner() } /// Returns a parallel iterator over the query results for the given [`World`]. @@ -1616,7 +1335,7 @@ impl QueryState { /// # let wrong_entity = Entity::from_raw(57); /// # let invalid_entity = world.spawn_empty().id(); /// - /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::NoSuchEntity(entity, _) => entity, _ => panic!()}, wrong_entity); + /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity); /// # assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` @@ -1629,16 +1348,7 @@ impl QueryState { /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool #[inline] pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, D, F> { - self.update_archetypes(world); - let this_run = world.change_tick(); - let last_run = world.last_change_tick(); - QueryParIter { - world: world.as_unsafe_world_cell(), - state: self, - last_run, - this_run, - batching_strategy: BatchingStrategy::new(), - } + self.query_mut(world).par_iter_inner() } /// Runs `func` on each query result in parallel for the given [`World`], where the last change and @@ -1671,7 +1381,8 @@ impl QueryState { INIT: Fn() -> T + Sync + Send + Clone, { // 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 + // 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 use arrayvec::ArrayVec; bevy_tasks::ComputeTaskPool::get().scope(|scope| { @@ -1692,7 +1403,9 @@ impl QueryState { scope.spawn(async move { #[cfg(feature = "trace")] let _span = self.par_iter_span.enter(); - let mut iter = self.iter_unchecked_manual(world, last_run, this_run); + let mut iter = self + .query_unchecked_manual_with_ticks(world, last_run, this_run) + .into_iter(); let mut accum = init_accum(); for storage_id in queue { accum = iter.fold_over_storage_range(accum, &mut func, storage_id, None); @@ -1711,7 +1424,8 @@ impl QueryState { #[cfg(feature = "trace")] let _span = self.par_iter_span.enter(); let accum = init_accum(); - self.iter_unchecked_manual(world, last_run, this_run) + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .into_iter() .fold_over_storage_range(accum, &mut func, storage_id, Some(batch)); }); } @@ -1751,6 +1465,132 @@ impl QueryState { }); } + /// Runs `func` on each query result in parallel for the given [`EntitySet`], + /// where the last change and the current change tick are given. This is faster than the + /// equivalent `iter_many_unique()` method, but cannot be chained like a normal [`Iterator`]. + /// + /// # Panics + /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` + /// with a mismatched [`WorldId`] is unsound. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, T, FN, INIT, E>( + &self, + init_accum: INIT, + world: UnsafeWorldCell<'w>, + entity_list: &UniqueEntitySlice, + batch_size: usize, + 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, + { + // 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 remainder = chunks.remainder(); + + for batch in chunks { + let mut func = func.clone(); + let init_accum = init_accum.clone(); + scope.spawn(async move { + #[cfg(feature = "trace")] + let _span = self.par_iter_span.enter(); + let accum = init_accum(); + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .iter_many_unique_inner(batch) + .fold(accum, &mut func); + }); + } + + #[cfg(feature = "trace")] + let _span = self.par_iter_span.enter(); + let accum = init_accum(); + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .iter_many_unique_inner(remainder) + .fold(accum, &mut func); + }); + } +} + +impl QueryState { + /// Runs `func` on each read-only query result in parallel for the given [`Entity`] list, + /// where the last change and the current change tick are given. This is faster than the equivalent + /// `iter_many()` method, but cannot be chained like a normal [`Iterator`]. + /// + /// # Panics + /// The [`ComputeTaskPool`] is not initialized. If using this from a query that is being + /// initialized and run from the ECS scheduler, this should never panic. + /// + /// # Safety + /// + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` + /// with a mismatched [`WorldId`] is unsound. + /// + /// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool + #[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))] + pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, T, FN, INIT, E>( + &self, + init_accum: INIT, + world: UnsafeWorldCell<'w>, + entity_list: &[E], + batch_size: usize, + 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, + { + // 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 remainder = chunks.remainder(); + + for batch in chunks { + let mut func = func.clone(); + let init_accum = init_accum.clone(); + scope.spawn(async move { + #[cfg(feature = "trace")] + let _span = self.par_iter_span.enter(); + let accum = init_accum(); + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .iter_many_inner(batch) + .fold(accum, &mut func); + }); + } + + #[cfg(feature = "trace")] + let _span = self.par_iter_span.enter(); + let accum = init_accum(); + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .iter_many_inner(remainder) + .fold(accum, &mut func); + }); + } +} + +impl QueryState { /// Returns a single immutable query result when there is exactly one entity matching /// the query. /// @@ -1764,10 +1604,7 @@ impl QueryState { #[track_caller] #[inline] pub fn single<'w>(&mut self, world: &'w World) -> ROQueryItem<'w, D> { - match self.get_single(world) { - Ok(items) => items, - Err(error) => panic!("Cannot get single query result: {error}"), - } + self.query(world).single_inner() } /// Returns a single immutable query result when there is exactly one entity matching @@ -1783,16 +1620,7 @@ impl QueryState { &mut self, world: &'w World, ) -> Result, QuerySingleError> { - self.update_archetypes(world); - - // SAFETY: query is read only - unsafe { - self.as_readonly().get_single_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.query(world).get_single_inner() } /// Returns a single mutable query result when there is exactly one entity matching @@ -1805,11 +1633,7 @@ impl QueryState { #[track_caller] #[inline] pub fn single_mut<'w>(&mut self, world: &'w mut World) -> D::Item<'w> { - // SAFETY: query has unique world access - match self.get_single_mut(world) { - Ok(items) => items, - Err(error) => panic!("Cannot get single query result: {error}"), - } + self.query_mut(world).single_inner() } /// Returns a single mutable query result when there is exactly one entity matching @@ -1822,18 +1646,7 @@ impl QueryState { &mut self, world: &'w mut World, ) -> Result, QuerySingleError> { - self.update_archetypes(world); - - let change_tick = world.change_tick(); - let last_change_tick = world.last_change_tick(); - // SAFETY: query has unique world access - unsafe { - self.get_single_unchecked_manual( - world.as_unsafe_world_cell(), - last_change_tick, - change_tick, - ) - } + self.query_mut(world).get_single_inner() } /// Returns a query result when there is exactly one entity matching the query. @@ -1850,8 +1663,7 @@ impl QueryState { &mut self, world: UnsafeWorldCell<'w>, ) -> Result, QuerySingleError> { - self.update_archetypes_unsafe_world_cell(world); - self.get_single_unchecked_manual(world, world.last_change_tick(), world.change_tick()) + self.query_unchecked(world).get_single_inner() } /// Returns a query result when there is exactly one entity matching the query, @@ -1864,6 +1676,8 @@ impl QueryState { /// /// This does not check for mutable query correctness. To be safe, make sure mutable queries /// have unique access to the components they query. + /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` + /// with a mismatched [`WorldId`] is unsound. #[inline] pub unsafe fn get_single_unchecked_manual<'w>( &self, @@ -1871,17 +1685,11 @@ impl QueryState { last_run: Tick, this_run: Tick, ) -> Result, QuerySingleError> { - let mut query = self.iter_unchecked_manual(world, last_run, this_run); - let first = query.next(); - let extra = query.next().is_some(); - - match (first, extra) { - (Some(r), false) => Ok(r), - (None, _) => Err(QuerySingleError::NoEntities(core::any::type_name::())), - (Some(_), _) => Err(QuerySingleError::MultipleEntities(core::any::type_name::< - Self, - >())), - } + // SAFETY: + // - The caller ensured we have the correct access to the world. + // - The caller ensured that the world matches. + self.query_unchecked_manual_with_ticks(world, last_run, this_run) + .get_single_inner() } } @@ -1893,86 +1701,10 @@ impl From> for QueryState = (0..10).map(|_| world.spawn_empty().id()).collect(); - - let query_state = world.query::(); - - // These don't matter for the test - let last_change_tick = world.last_change_tick(); - let change_tick = world.change_tick(); - - // It's best to test get_many_unchecked_manual directly, - // as it is shared and unsafe - // We don't care about aliased mutability for the read-only equivalent - - // SAFETY: Query does not access world data. - assert!(unsafe { - query_state - .get_many_unchecked_manual::<10>( - world.as_unsafe_world_cell_readonly(), - entities.clone().try_into().unwrap(), - last_change_tick, - change_tick, - ) - .is_ok() - }); - - assert_eq!( - // SAFETY: Query does not access world data. - unsafe { - query_state - .get_many_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - [entities[0], entities[0]], - last_change_tick, - change_tick, - ) - .unwrap_err() - }, - QueryEntityError::AliasedMutability(entities[0]) - ); - - assert_eq!( - // SAFETY: Query does not access world data. - unsafe { - query_state - .get_many_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - [entities[0], entities[1], entities[0]], - last_change_tick, - change_tick, - ) - .unwrap_err() - }, - QueryEntityError::AliasedMutability(entities[0]) - ); - - assert_eq!( - // SAFETY: Query does not access world data. - unsafe { - query_state - .get_many_unchecked_manual( - world.as_unsafe_world_cell_readonly(), - [entities[9], entities[9]], - last_change_tick, - change_tick, - ) - .unwrap_err() - }, - QueryEntityError::AliasedMutability(entities[9]) - ); - } #[test] #[should_panic] @@ -2338,8 +2070,8 @@ mod tests { world.spawn((B(0), C(0))); world.spawn(C(0)); - let mut df = DefaultQueryFilters::default(); - df.set_disabled(world.register_component::()); + let mut df = DefaultQueryFilters::empty(); + df.register_disabling_component(world.register_component::()); world.insert_resource(df); // Without only matches the first entity @@ -2378,8 +2110,8 @@ mod tests { assert!(query.is_dense); assert_eq!(3, query.iter(&world).count()); - let mut df = DefaultQueryFilters::default(); - df.set_disabled(world.register_component::()); + let mut df = DefaultQueryFilters::empty(); + df.register_disabling_component(world.register_component::()); world.insert_resource(df); let mut query = QueryState::<()>::new(&mut world); @@ -2388,8 +2120,8 @@ mod tests { assert!(!query.is_dense); assert_eq!(1, query.iter(&world).count()); - let mut df = DefaultQueryFilters::default(); - df.set_disabled(world.register_component::()); + let mut df = DefaultQueryFilters::empty(); + df.register_disabling_component(world.register_component::
()); world.insert_resource(df); let mut query = QueryState::<()>::new(&mut world); diff --git a/crates/bevy_ecs/src/query/world_query.rs b/crates/bevy_ecs/src/query/world_query.rs index dbbe99b6c5e8a..da147770e0fcf 100644 --- a/crates/bevy_ecs/src/query/world_query.rs +++ b/crates/bevy_ecs/src/query/world_query.rs @@ -1,9 +1,8 @@ use crate::{ archetype::Archetype, component::{ComponentId, Components, Tick}, - entity::Entity, query::FilteredAccess, - storage::{Table, TableRow}, + storage::Table, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use variadics_please::all_tuples; @@ -14,11 +13,11 @@ use variadics_please::all_tuples; /// # Safety /// /// Implementor must ensure that -/// [`update_component_access`], [`matches_component_set`], [`fetch`] and [`init_fetch`] +/// [`update_component_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`] /// obey the following: /// -/// - For each component mutably accessed by [`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 [`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`] 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. /// - 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. @@ -31,7 +30,8 @@ use variadics_please::all_tuples; /// /// 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. /// -/// [`fetch`]: Self::fetch +/// [`QueryData::fetch`]: crate::query::QueryData::fetch +/// [`QueryFilter::filter_fetch`]: crate::query::QueryFilter::filter_fetch /// [`init_fetch`]: Self::init_fetch /// [`matches_component_set`]: Self::matches_component_set /// [`Query`]: crate::system::Query @@ -39,19 +39,7 @@ use variadics_please::all_tuples; /// [`QueryData`]: crate::query::QueryData /// [`QueryFilter`]: crate::query::QueryFilter pub unsafe trait WorldQuery { - /// The item returned by this [`WorldQuery`] - /// For `QueryData` this will be the data retrieved by the query, - /// and is visible to the end user when calling e.g. `Query::get`. - /// - /// For `QueryFilter` this will be either `()`, or a `bool` indicating whether the entity should be included - /// or a tuple of such things. - /// Archetypal query filters (like `With`) set this to `()`, - /// as the filtering is done by selecting the archetypes to iterate over via [`WorldQuery::matches_component_set`], - /// while non-archetypal query filters (like `Changed`) set this to a `bool` and evaluate the filter for each entity, - /// after the set of possible archetypes has been narrowed down. - type Item<'a>; - - /// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](WorldQuery::Item) for each entity. + /// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](crate::query::QueryData::Item) for each entity. type Fetch<'a>: Clone; /// State used to construct a [`Self::Fetch`](WorldQuery::Fetch). This will be cached inside [`QueryState`](crate::query::QueryState), @@ -59,9 +47,6 @@ pub unsafe trait WorldQuery { /// constructing [`Self::Fetch`](WorldQuery::Fetch). type State: Send + Sync + Sized; - /// This function manually implements subtyping for the query items. - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>; - /// This function manually implements subtyping for the query fetches. fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort>; @@ -87,8 +72,8 @@ pub unsafe trait WorldQuery { /// /// This is used to select a more efficient "table iterator" /// for "dense" queries. If this returns true, [`WorldQuery::set_table`] must be used before - /// [`WorldQuery::fetch`] can be called for iterators. If this returns false, - /// [`WorldQuery::set_archetype`] must be used before [`WorldQuery::fetch`] can be called for + /// [`QueryData::fetch`](crate::query::QueryData::fetch) can be called for iterators. If this returns false, + /// [`WorldQuery::set_archetype`] must be used before [`QueryData::fetch`](crate::query::QueryData::fetch) can be called for /// iterators. const IS_DENSE: bool; @@ -122,23 +107,6 @@ pub unsafe trait WorldQuery { /// 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) {} - /// Fetch [`Self::Item`](`WorldQuery::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 - /// [`WorldQuery::set_archetype`] with an `entity` in the current archetype. - /// Accesses components registered in [`WorldQuery::update_component_access`]. - /// - /// # Safety - /// - /// - Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and - /// `table_row` must be in the range of the current table and archetype. - /// - There must not be simultaneous conflicting component access registered in `update_component_access`. - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w>; - /// Adds any component accesses used by this [`WorldQuery`] to `access`. /// /// Used to check which queries are disjoint and can run in parallel @@ -191,15 +159,8 @@ macro_rules! impl_tuple_world_query { /// This is sound because `matches_component_set` always returns `false` if any the subqueries' implementations return `false`. unsafe impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { type Fetch<'w> = ($($name::Fetch<'w>,)*); - type Item<'w> = ($($name::Item<'w>,)*); type State = ($($name::State,)*); - fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { - let ($($name,)*) = item; - ($( - $name::shrink($name), - )*) - } fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> { let ($($name,)*) = fetch; @@ -238,16 +199,6 @@ macro_rules! impl_tuple_world_query { $(unsafe { $name::set_table($name, $state, table); })* } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow - ) -> Self::Item<'w> { - let ($($name,)*) = fetch; - // SAFETY: The invariants are upheld by the caller. - ($(unsafe { $name::fetch($name, entity, table_row) },)*) - } fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { let ($($name,)*) = state; diff --git a/crates/bevy_ecs/src/reflect/bundle.rs b/crates/bevy_ecs/src/reflect/bundle.rs index 248ca1a7048c1..b7acf69d6aad9 100644 --- a/crates/bevy_ecs/src/reflect/bundle.rs +++ b/crates/bevy_ecs/src/reflect/bundle.rs @@ -8,6 +8,8 @@ use alloc::boxed::Box; use core::any::{Any, TypeId}; use crate::{ + bundle::BundleFromComponents, + entity::EntityMapper, prelude::Bundle, world::{EntityMut, EntityWorldMut}, }; @@ -33,8 +35,9 @@ pub struct ReflectBundleFns { pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), /// Function pointer implementing [`ReflectBundle::apply`]. pub apply: fn(EntityMut, &dyn PartialReflect, &TypeRegistry), - /// Function pointer implementing [`ReflectBundle::apply_or_insert`]. - pub apply_or_insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), + /// Function pointer implementing [`ReflectBundle::apply_or_insert_mapped`]. + pub apply_or_insert_mapped: + fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper), /// Function pointer implementing [`ReflectBundle::remove`]. pub remove: fn(&mut EntityWorldMut), /// Function pointer implementing [`ReflectBundle::take`]. @@ -47,7 +50,7 @@ impl ReflectBundleFns { /// /// This is useful if you want to start with the default implementation before overriding some /// of the functions to create a custom implementation. - pub fn new() -> Self { + pub fn new() -> Self { >::from_type().0 } } @@ -78,13 +81,14 @@ impl ReflectBundle { } /// Uses reflection to set the value of this [`Bundle`] type in the entity to the given value or insert a new one if it does not exist. - pub fn apply_or_insert( + pub fn apply_or_insert_mapped( &self, entity: &mut EntityWorldMut, bundle: &dyn PartialReflect, registry: &TypeRegistry, + mapper: &mut dyn EntityMapper, ) { - (self.0.apply_or_insert)(entity, bundle, registry); + (self.0.apply_or_insert_mapped)(entity, bundle, registry, mapper); } /// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist. @@ -136,7 +140,7 @@ impl ReflectBundle { } } -impl FromType for ReflectBundle { +impl FromType for ReflectBundle { fn from_type() -> Self { ReflectBundle(ReflectBundleFns { insert: |entity, reflected_bundle, registry| { @@ -166,19 +170,24 @@ impl FromType for ReflectBundle { } } }, - apply_or_insert: |entity, reflected_bundle, registry| { + apply_or_insert_mapped: |entity, reflected_bundle, registry, mapper| { if let Some(reflect_component) = registry.get_type_data::(TypeId::of::()) { - reflect_component.apply_or_insert(entity, reflected_bundle, registry); + reflect_component.apply_or_insert_mapped( + entity, + reflected_bundle, + registry, + mapper, + ); } else { match reflected_bundle.reflect_ref() { - ReflectRef::Struct(bundle) => bundle - .iter_fields() - .for_each(|field| apply_or_insert_field(entity, field, registry)), - ReflectRef::Tuple(bundle) => bundle - .iter_fields() - .for_each(|field| apply_or_insert_field(entity, field, registry)), + ReflectRef::Struct(bundle) => bundle.iter_fields().for_each(|field| { + apply_or_insert_field_mapped(entity, field, registry, mapper); + }), + ReflectRef::Tuple(bundle) => bundle.iter_fields().for_each(|field| { + apply_or_insert_field_mapped(entity, field, registry, mapper); + }), _ => panic!( "expected bundle `{}` to be a named struct or tuple", // FIXME: once we have unique reflect, use `TypePath`. @@ -218,10 +227,11 @@ fn apply_field(entity: &mut EntityMut, field: &dyn PartialReflect, registry: &Ty } } -fn apply_or_insert_field( +fn apply_or_insert_field_mapped( entity: &mut EntityWorldMut, field: &dyn PartialReflect, registry: &TypeRegistry, + mapper: &mut dyn EntityMapper, ) { let Some(type_id) = field.try_as_reflect().map(Any::type_id) else { panic!( @@ -231,9 +241,9 @@ fn apply_or_insert_field( }; if let Some(reflect_component) = registry.get_type_data::(type_id) { - reflect_component.apply_or_insert(entity, field, registry); + reflect_component.apply_or_insert_mapped(entity, field, registry, mapper); } else if let Some(reflect_bundle) = registry.get_type_data::(type_id) { - reflect_bundle.apply_or_insert(entity, field, registry); + reflect_bundle.apply_or_insert_mapped(entity, field, registry, mapper); } else { let is_component = entity.world().components().get_id(type_id).is_some(); diff --git a/crates/bevy_ecs/src/reflect/component.rs b/crates/bevy_ecs/src/reflect/component.rs index 6af8e34a31304..bffd2e9c290b7 100644 --- a/crates/bevy_ecs/src/reflect/component.rs +++ b/crates/bevy_ecs/src/reflect/component.rs @@ -61,7 +61,7 @@ use super::from_reflect_with_fallback; use crate::{ change_detection::Mut, component::{ComponentId, ComponentMutability}, - entity::Entity, + entity::{Entity, EntityMapper}, prelude::Component, world::{ unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut, @@ -104,8 +104,9 @@ pub struct ReflectComponentFns { pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), /// Function pointer implementing [`ReflectComponent::apply()`]. pub apply: fn(EntityMut, &dyn PartialReflect), - /// Function pointer implementing [`ReflectComponent::apply_or_insert()`]. - pub apply_or_insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), + /// Function pointer implementing [`ReflectComponent::apply_or_insert_mapped()`]. + pub apply_or_insert_mapped: + fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper), /// Function pointer implementing [`ReflectComponent::remove()`]. pub remove: fn(&mut EntityWorldMut), /// Function pointer implementing [`ReflectComponent::contains()`]. @@ -114,6 +115,10 @@ 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::reflect_unchecked_mut()`]. /// /// # Safety @@ -163,13 +168,14 @@ impl ReflectComponent { /// # Panics /// /// Panics if [`Component`] is immutable. - pub fn apply_or_insert( + pub fn apply_or_insert_mapped( &self, entity: &mut EntityWorldMut, component: &dyn PartialReflect, registry: &TypeRegistry, + map: &mut dyn EntityMapper, ) { - (self.0.apply_or_insert)(entity, component, registry); + (self.0.apply_or_insert_mapped)(entity, component, registry, map); } /// Removes this [`Component`] type from the entity. Does nothing if it doesn't exist. @@ -277,6 +283,20 @@ impl ReflectComponent { pub fn fn_pointers(&self) -> &ReflectComponentFns { &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); + } } impl FromType for ReflectComponent { @@ -300,21 +320,28 @@ impl FromType for ReflectComponent { let mut component = unsafe { entity.get_mut_assume_mutable::() }.unwrap(); component.apply(reflected_component); }, - apply_or_insert: |entity, reflected_component, registry| { + apply_or_insert_mapped: |entity, reflected_component, registry, mapper| { + // TODO: if we can externalize this impl to cut down on monomorphization that would be great + let map_fn = move |entity: &mut Entity| { + *entity = mapper.get_mapped(*entity); + }; 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); } else { - let component = entity.world_scope(|world| { + let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); + C::visit_entities_mut(&mut component, map_fn); entity.insert(component); } } else { - let component = entity.world_scope(|world| { + let mut component = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_component, world, registry) }); + C::visit_entities_mut(&mut component, map_fn); entity.insert(component); } }, @@ -359,6 +386,14 @@ 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)| { + let component = reflect.downcast_mut::().unwrap(); + Component::visit_entities_mut(component, func); + }, }) } } diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 3d5dd6815fc78..9aef8105a53ca 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -388,7 +388,6 @@ fn remove_reflect_with_registry_ref( #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, bundle::Bundle, component::Component, prelude::{AppTypeRegistry, ReflectComponent}, diff --git a/crates/bevy_ecs/src/reflect/mod.rs b/crates/bevy_ecs/src/reflect/mod.rs index 9d30fea57b00a..4d94945afc1e9 100644 --- a/crates/bevy_ecs/src/reflect/mod.rs +++ b/crates/bevy_ecs/src/reflect/mod.rs @@ -5,7 +5,6 @@ use core::{ ops::{Deref, DerefMut}, }; -use crate as bevy_ecs; use crate::{resource::Resource, world::World}; use bevy_reflect::{ std_traits::ReflectDefault, PartialReflect, Reflect, ReflectFromReflect, TypePath, diff --git a/crates/bevy_ecs/src/reflect/resource.rs b/crates/bevy_ecs/src/reflect/resource.rs index 238fd5f4e77ae..34e593a6ef00e 100644 --- a/crates/bevy_ecs/src/reflect/resource.rs +++ b/crates/bevy_ecs/src/reflect/resource.rs @@ -8,7 +8,7 @@ use crate::{ change_detection::Mut, component::ComponentId, resource::Resource, - world::{unsafe_world_cell::UnsafeWorldCell, World}, + world::{unsafe_world_cell::UnsafeWorldCell, FilteredResources, FilteredResourcesMut, World}, }; use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry}; @@ -52,7 +52,9 @@ pub struct ReflectResourceFns { /// Function pointer implementing [`ReflectResource::remove()`]. pub remove: fn(&mut World), /// Function pointer implementing [`ReflectResource::reflect()`]. - pub reflect: fn(&World) -> Option<&dyn Reflect>, + pub reflect: for<'w> fn(FilteredResources<'w, '_>) -> Option<&'w dyn Reflect>, + /// Function pointer implementing [`ReflectResource::reflect_mut()`]. + pub reflect_mut: for<'w> fn(FilteredResourcesMut<'w, '_>) -> Option>, /// Function pointer implementing [`ReflectResource::reflect_unchecked_mut()`]. /// /// # Safety @@ -111,14 +113,23 @@ impl ReflectResource { } /// Gets the value of this [`Resource`] type from the world as a reflected reference. - pub fn reflect<'a>(&self, world: &'a World) -> Option<&'a dyn Reflect> { - (self.0.reflect)(world) + /// + /// Note that [`&World`](World) is a valid type for `resources`. + pub fn reflect<'w, 's>( + &self, + resources: impl Into>, + ) -> Option<&'w dyn Reflect> { + (self.0.reflect)(resources.into()) } /// Gets the value of this [`Resource`] type from the world as a mutable reflected reference. - pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option> { - // SAFETY: unique world access - unsafe { (self.0.reflect_unchecked_mut)(world.as_unsafe_world_cell()) } + /// + /// Note that [`&mut World`](World) is a valid type for `resources`. + pub fn reflect_mut<'w, 's>( + &self, + resources: impl Into>, + ) -> Option> { + (self.0.reflect_mut)(resources.into()) } /// # Safety @@ -212,7 +223,12 @@ impl FromType for ReflectResource { remove: |world| { world.remove_resource::(); }, - reflect: |world| world.get_resource::().map(|res| res as &dyn Reflect), + reflect: |world| world.get::().map(|res| res.into_inner() as &dyn Reflect), + reflect_mut: |world| { + world + .into_mut::() + .map(|res| res.map_unchanged(|value| value as &mut dyn Reflect)) + }, reflect_unchecked_mut: |world| { // SAFETY: all usages of `reflect_unchecked_mut` guarantee that there is either a single mutable // reference or multiple immutable ones alive at any given point diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index b04b4f2b58887..540b049541bd9 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -12,11 +12,11 @@ pub use relationship_source_collection::*; use crate::{ component::{Component, HookContext, Mutable}, - entity::Entity, + entity::{ComponentCloneCtx, Entity}, system::{ command::HandleError, entity_command::{self, CommandWithEntity}, - error_handler, + error_handler, Commands, }, world::{DeferredWorld, EntityWorldMut}, }; @@ -47,7 +47,7 @@ use log::warn; /// pub struct Children(Vec); /// ``` /// -/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(despawn_descendants)]` attribute to +/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(linked_spawn)]` attribute to /// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned: /// /// ``` @@ -58,7 +58,7 @@ use log::warn; /// pub struct ChildOf(pub Entity); /// /// #[derive(Component)] -/// #[relationship_target(relationship = ChildOf, despawn_descendants)] +/// #[relationship_target(relationship = ChildOf, linked_spawn)] /// pub struct Children(Vec); /// ``` pub trait Relationship: Component + Sized { @@ -83,6 +83,7 @@ pub trait Relationship: Component + Sized { core::any::type_name::() ); world.commands().entity(entity).remove::(); + return; } if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { if let Some(mut relationship_target) = @@ -142,6 +143,14 @@ pub type SourceIter<'w, R> = /// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. /// See the [`Relationship`] documentation for more information. pub trait RelationshipTarget: Component + Sized { + /// If this is true, when despawning or cloning (when [recursion is enabled](crate::entity::EntityClonerBuilder::recursive)), the related entities targeting this entity will also be despawned or cloned. + /// + /// For example, this is set to `true` for Bevy's built-in parent-child relation, defined by [`ChildOf`](crate::prelude::ChildOf) and [`Children`](crate::prelude::Children). + /// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning). + /// + /// To get around this behavior, you can first break the relationship between entities, and *then* despawn or clone. + /// This defaults to false when derived. + const LINKED_SPAWN: bool; /// The [`Relationship`] that populates this [`RelationshipTarget`] collection. type Relationship: Relationship; /// The collection type that stores the "source" entities for this [`RelationshipTarget`] component. @@ -177,7 +186,7 @@ pub trait RelationshipTarget: Component + Sized { let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); let mut commands = world.get_raw_command_queue(); for source_entity in relationship_target.iter() { - if world.get_entity(source_entity).is_some() { + if world.get_entity(source_entity).is_ok() { commands.push( entity_command::remove::() .with_entity(source_entity) @@ -208,7 +217,7 @@ pub trait RelationshipTarget: Component + Sized { let relationship_target = world.get_entity(entity).unwrap().get::().unwrap(); let mut commands = world.get_raw_command_queue(); for source_entity in relationship_target.iter() { - if world.get_entity(source_entity).is_some() { + if world.get_entity(source_entity).is_ok() { commands.push( entity_command::despawn() .with_entity(source_entity) @@ -253,9 +262,30 @@ pub trait RelationshipTarget: Component + Sized { } } +/// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty +/// [`RelationshipTarget`] instance with space reserved for the number of targets in the +/// original instance. The [`RelationshipTarget`] will then be populated with the proper components +/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities +/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that! +/// +/// 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, + context: &mut ComponentCloneCtx, +) { + if let Some(component) = context.read_source_component::() { + if context.is_recursive() && T::LINKED_SPAWN { + for entity in component.iter() { + context.queue_entity_clone(entity); + } + } + context.write_target_component(T::with_capacity(component.len())); + } +} + #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::world::World; use crate::{component::Component, entity::Entity}; use alloc::vec::Vec; @@ -276,4 +306,39 @@ mod tests { let c = world.spawn(Likes(a)).id(); assert_eq!(world.entity(a).get::().unwrap().0, &[b, c]); } + + #[test] + fn self_relationship_fails() { + #[derive(Component)] + #[relationship(relationship_target = RelTarget)] + struct Rel(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Rel)] + struct RelTarget(Vec); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + world.entity_mut(a).insert(Rel(a)); + assert!(!world.entity(a).contains::()); + assert!(!world.entity(a).contains::()); + } + + #[test] + fn relationship_with_missing_target_fails() { + #[derive(Component)] + #[relationship(relationship_target = RelTarget)] + struct Rel(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Rel)] + struct RelTarget(Vec); + + let mut world = World::new(); + let a = world.spawn_empty().id(); + world.despawn(a); + let b = world.spawn(Rel(a)).id(); + assert!(!world.entity(b).contains::()); + assert!(!world.entity(b).contains::()); + } } diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index ea8bfb979fd77..150bd02ebdbe6 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -249,7 +249,6 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { #[cfg(test)] mod tests { use super::*; - use crate as bevy_ecs; use crate::prelude::{ChildOf, Children, Component}; #[derive(Component, Clone, Copy)] diff --git a/crates/bevy_ecs/src/relationship/relationship_query.rs b/crates/bevy_ecs/src/relationship/relationship_query.rs index cc7f66fb3a064..a2ec937c29b9a 100644 --- a/crates/bevy_ecs/src/relationship/relationship_query.rs +++ b/crates/bevy_ecs/src/relationship/relationship_query.rs @@ -1,6 +1,6 @@ use crate::{ entity::Entity, - query::{QueryData, QueryFilter, WorldQuery}, + query::{QueryData, QueryFilter}, relationship::{Relationship, RelationshipTarget}, system::Query, }; @@ -14,7 +14,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// target entity of that relationship. pub fn related(&'w self, entity: Entity) -> Option where - ::ReadOnly: WorldQuery = &'w R>, + ::ReadOnly: QueryData = &'w R>, { self.get(entity).map(R::get).ok() } @@ -26,7 +26,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - ::ReadOnly: WorldQuery = &'w S>, + ::ReadOnly: QueryData = &'w S>, { self.get(entity) .into_iter() @@ -42,7 +42,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// If your relationship is not a tree (like Bevy's hierarchy), be sure to stop if you encounter a duplicate entity. pub fn root_ancestor(&'w self, entity: Entity) -> Entity where - ::ReadOnly: WorldQuery = &'w R>, + ::ReadOnly: QueryData = &'w R>, { // Recursively search up the tree until we're out of parents match self.get(entity) { @@ -62,7 +62,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - ::ReadOnly: WorldQuery = &'w S>, + ::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { self.iter_descendants_depth_first(entity).filter(|entity| { @@ -80,7 +80,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> impl Iterator + 'w where - D::ReadOnly: WorldQuery = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, + D::ReadOnly: QueryData = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>, { self.get(entity) .ok() @@ -103,7 +103,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, { DescendantIter::new(self, entity) } @@ -120,7 +120,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { DescendantDepthFirstIter::new(self, entity) @@ -137,7 +137,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { entity: Entity, ) -> AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: WorldQuery = &'w R>, + D::ReadOnly: QueryData = &'w R>, { AncestorIter::new(self, entity) } @@ -148,7 +148,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Traverses the hierarchy breadth-first. pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, { children_query: &'w Query<'w, 's, D, F>, vecdeque: VecDeque, @@ -156,7 +156,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, { /// Returns a new [`DescendantIter`]. pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { @@ -174,7 +174,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, { type Item = Entity; @@ -194,7 +194,7 @@ where /// Traverses the hierarchy depth-first. pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, { children_query: &'w Query<'w, 's, D, F>, stack: SmallVec<[Entity; 8]>, @@ -203,7 +203,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { /// Returns a new [`DescendantDepthFirstIter`]. @@ -220,7 +220,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator for DescendantDepthFirstIter<'w, 's, D, F, S> where - D::ReadOnly: WorldQuery = &'w S>, + D::ReadOnly: QueryData = &'w S>, SourceIter<'w, S>: DoubleEndedIterator, { type Item = Entity; @@ -239,7 +239,7 @@ where /// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`]. pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> where - D::ReadOnly: WorldQuery = &'w R>, + D::ReadOnly: QueryData = &'w R>, { parent_query: &'w Query<'w, 's, D, F>, next: Option, @@ -247,7 +247,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: WorldQuery = &'w R>, + D::ReadOnly: QueryData = &'w R>, { /// Returns a new [`AncestorIter`]. pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self { @@ -261,7 +261,7 @@ where impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> Iterator for AncestorIter<'w, 's, D, F, R> where - D::ReadOnly: WorldQuery = &'w R>, + D::ReadOnly: QueryData = &'w R>, { type Item = Entity; diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index d9b26a720d44b..013cdd63aaf2b 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -119,7 +119,6 @@ impl RelationshipSourceCollection for SmallVec<[Entity; N]> { #[cfg(test)] mod tests { use super::*; - use crate as bevy_ecs; use crate::prelude::{Component, World}; use crate::relationship::RelationshipTarget; @@ -130,7 +129,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(Vec); let mut world = World::new(); @@ -151,7 +150,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(EntityHashSet); let mut world = World::new(); @@ -172,7 +171,7 @@ mod tests { struct Rel(Entity); #[derive(Component)] - #[relationship_target(relationship = Rel, despawn_descendants)] + #[relationship_target(relationship = Rel, linked_spawn)] struct RelTarget(SmallVec<[Entity; 4]>); let mut world = World::new(); diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index c81a5cfa1eb2c..a1480a1923144 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -1,7 +1,6 @@ //! Alerting events when a component is removed from an entity. use crate::{ - self as bevy_ecs, component::{Component, ComponentId, ComponentIdFor, Tick}, entity::Entity, event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events}, diff --git a/crates/bevy_ecs/src/result.rs b/crates/bevy_ecs/src/result.rs index 77e7fc00743ae..ef5eace90b07e 100644 --- a/crates/bevy_ecs/src/result.rs +++ b/crates/bevy_ecs/src/result.rs @@ -1,9 +1,153 @@ -//! Contains error and result helpers for use in fallible systems. +//! Error handling for "fallible" systems. +//! +//! 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 [`Error`]s returned by a system 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`] sets the global error handler for all systems of the +//! current [`World`]. +//! - [`Schedule::set_error_handler`] sets the error handler for all systems of that schedule. +//! +//! Bevy provides a number of pre-built error-handlers for you to use: +//! +//! - [`panic`] – panics with the system error +//! - [`error`] – logs the system error at the `error` level +//! - [`warn`] – logs the system error at the `warn` level +//! - [`info`] – logs the system error at the `info` level +//! - [`debug`] – logs the system error at the `debug` level +//! - [`trace`] – logs the system error at the `trace` level +//! - [`ignore`] – ignores the system error +//! +//! However, you can use any custom error handler logic by providing your own function (or +//! non-capturing closure that coerces to the function signature) as long as it matches the +//! signature: +//! +//! ```rust,ignore +//! fn(Error, SystemErrorContext) +//! ``` +//! +//! 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. +//! +//! For example: +//! +//! ```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; +//! } +//! +//! bevy_ecs::result::error(error, ctx); +//! }); +//! # } +//! ``` +//! +//! 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. +//! +//! [`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 -use alloc::boxed::Box; +use crate::{component::Tick, resource::Resource}; +use alloc::{borrow::Cow, boxed::Box}; /// A dynamic error type for use in fallible systems. pub type Error = Box; /// A result type for use in fallible systems. pub type Result = core::result::Result; + +/// 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, +} + +/// The default systems error handler stored as a resource in the [`World`](crate::world::World). +pub struct DefaultSystemErrorHandler(pub fn(Error, SystemErrorContext)); + +impl Resource for DefaultSystemErrorHandler {} + +impl Default for DefaultSystemErrorHandler { + fn default() -> Self { + Self(panic) + } +} + +macro_rules! inner { + ($call:path, $e:ident, $c:ident) => { + $call!("Encountered an error in system `{}`: {:?}", $c.name, $e); + }; +} + +/// Error handler that panics with the system error. +#[track_caller] +#[inline] +pub fn panic(error: Error, ctx: SystemErrorContext) { + inner!(panic, error, ctx); +} + +/// Error handler that logs the system error at the `error` level. +#[track_caller] +#[inline] +pub fn error(error: Error, ctx: SystemErrorContext) { + inner!(log::error, error, ctx); +} + +/// Error handler that logs the system error at the `warn` level. +#[track_caller] +#[inline] +pub fn warn(error: Error, ctx: SystemErrorContext) { + inner!(log::warn, error, ctx); +} + +/// Error handler that logs the system error at the `info` level. +#[track_caller] +#[inline] +pub fn info(error: Error, ctx: SystemErrorContext) { + inner!(log::info, error, ctx); +} + +/// Error handler that logs the system error at the `debug` level. +#[track_caller] +#[inline] +pub fn debug(error: Error, ctx: SystemErrorContext) { + inner!(log::debug, error, ctx); +} + +/// Error handler that logs the system error at the `trace` level. +#[track_caller] +#[inline] +pub fn trace(error: Error, ctx: SystemErrorContext) { + inner!(log::trace, error, ctx); +} + +/// Error handler that ignores the system error. +#[track_caller] +#[inline] +pub fn ignore(_: Error, _: SystemErrorContext) {} diff --git a/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs b/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs new file mode 100644 index 0000000000000..8ad4725d86f95 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs @@ -0,0 +1,161 @@ +use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; + +use bevy_platform_support::collections::HashMap; + +use crate::system::IntoSystem; +use crate::world::World; + +use super::{ + is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError, + ScheduleBuildPass, ScheduleGraph, SystemNode, +}; + +/// A [`ScheduleBuildPass`] that inserts [`ApplyDeferred`] systems into the schedule graph +/// when there are [`Deferred`](crate::prelude::Deferred) +/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one +/// such deferred buffer. +/// +/// This pass is typically automatically added to the schedule. You can disable this by setting +/// [`ScheduleBuildSettings::auto_insert_apply_deferred`](crate::schedule::ScheduleBuildSettings::auto_insert_apply_deferred) +/// to `false`. You may want to disable this if you only want to sync deferred params at the end of the schedule, +/// or want to manually insert all your sync points. +#[derive(Debug, Default)] +pub struct AutoInsertApplyDeferredPass { + /// Dependency edges that will **not** automatically insert an instance of `ApplyDeferred` on the edge. + no_sync_edges: BTreeSet<(NodeId, NodeId)>, + auto_sync_node_ids: HashMap, +} + +/// If added to a dependency edge, the edge will not be considered for auto sync point insertions. +pub struct IgnoreDeferred; + +impl AutoInsertApplyDeferredPass { + /// Returns the `NodeId` of the cached auto sync point. Will create + /// a new one if needed. + fn get_sync_point(&mut self, graph: &mut ScheduleGraph, distance: u32) -> NodeId { + self.auto_sync_node_ids + .get(&distance) + .copied() + .or_else(|| { + let node_id = self.add_auto_sync(graph); + self.auto_sync_node_ids.insert(distance, node_id); + Some(node_id) + }) + .unwrap() + } + /// add an [`ApplyDeferred`] system with no config + fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> NodeId { + let id = NodeId::System(graph.systems.len()); + + graph + .systems + .push(SystemNode::new(Box::new(IntoSystem::into_system( + ApplyDeferred, + )))); + graph.system_conditions.push(Vec::new()); + + // ignore ambiguities with auto sync points + // They aren't under user control, so no one should know or care. + graph.ambiguous_with_all.insert(id); + + id + } +} + +impl ScheduleBuildPass for AutoInsertApplyDeferredPass { + type EdgeOptions = IgnoreDeferred; + + fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>) { + if options.is_some() { + self.no_sync_edges.insert((from, to)); + } + } + + fn build( + &mut self, + _world: &mut World, + graph: &mut ScheduleGraph, + dependency_flattened: &mut DiGraph, + ) -> Result<(), ScheduleBuildError> { + let mut sync_point_graph = dependency_flattened.clone(); + let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?; + + // calculate the number of sync points each sync point is from the beginning of the graph + // use the same sync point if the distance is the same + let mut distances: HashMap> = + HashMap::with_capacity_and_hasher(topo.len(), Default::default()); + for node in &topo { + let add_sync_after = graph.systems[node.index()].get().unwrap().has_deferred(); + + for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) { + let add_sync_on_edge = add_sync_after + && !is_apply_deferred(graph.systems[target.index()].get().unwrap()) + && !self.no_sync_edges.contains(&(*node, target)); + + let weight = if add_sync_on_edge { 1 } else { 0 }; + + let distance = distances + .get(&target.index()) + .unwrap_or(&None) + .or(Some(0)) + .map(|distance| { + distance.max( + distances.get(&node.index()).unwrap_or(&None).unwrap_or(0) + weight, + ) + }); + + distances.insert(target.index(), distance); + + if add_sync_on_edge { + let sync_point = + self.get_sync_point(graph, distances[&target.index()].unwrap()); + sync_point_graph.add_edge(*node, sync_point); + sync_point_graph.add_edge(sync_point, target); + + // edge is now redundant + sync_point_graph.remove_edge(*node, target); + } + } + } + + *dependency_flattened = sync_point_graph; + Ok(()) + } + + fn collapse_set( + &mut self, + set: NodeId, + systems: &[NodeId], + dependency_flattened: &DiGraph, + ) -> impl Iterator { + if systems.is_empty() { + // collapse dependencies for empty sets + for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) { + for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) { + if self.no_sync_edges.contains(&(a, set)) + && self.no_sync_edges.contains(&(set, b)) + { + self.no_sync_edges.insert((a, b)); + } + } + } + } else { + for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) { + for &sys in systems { + if self.no_sync_edges.contains(&(a, set)) { + self.no_sync_edges.insert((a, sys)); + } + } + } + + for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) { + for &sys in systems { + if self.no_sync_edges.contains(&(set, b)) { + self.no_sync_edges.insert((sys, b)); + } + } + } + } + core::iter::empty() + } +} diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index f80a0ad086aed..1bd0e9f4da304 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1263,7 +1263,6 @@ where #[cfg(test)] mod tests { use super::{common_conditions::*, Condition}; - use crate as bevy_ecs; 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 3d9deeceff5f9..898cf674245d2 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -4,6 +4,7 @@ use variadics_please::all_tuples; use crate::{ result::Result, schedule::{ + auto_insert_apply_deferred::IgnoreDeferred, condition::{BoxedCondition, Condition}, graph::{Ambiguity, Dependency, DependencyKind, GraphInfo}, set::{InternedSystemSet, IntoSystemSet, SystemSet}, @@ -137,7 +138,7 @@ impl NodeConfigs { config .graph_info .dependencies - .push(Dependency::new(DependencyKind::BeforeNoSync, set)); + .push(Dependency::new(DependencyKind::Before, set).add_config(IgnoreDeferred)); } Self::Configs { configs, .. } => { for config in configs { @@ -153,7 +154,7 @@ impl NodeConfigs { config .graph_info .dependencies - .push(Dependency::new(DependencyKind::AfterNoSync, set)); + .push(Dependency::new(DependencyKind::After, set).add_config(IgnoreDeferred)); } Self::Configs { configs, .. } => { for config in configs { @@ -224,9 +225,9 @@ impl NodeConfigs { match &mut self { Self::NodeConfig(_) => { /* no op */ } Self::Configs { chained, .. } => { - *chained = Chain::Yes; + chained.set_chained(); } - } + }; self } @@ -234,7 +235,7 @@ impl NodeConfigs { match &mut self { Self::NodeConfig(_) => { /* no op */ } Self::Configs { chained, .. } => { - *chained = Chain::YesIgnoreDeferred; + chained.set_chained_with_config(IgnoreDeferred); } } self @@ -582,7 +583,7 @@ macro_rules! impl_system_collection { SystemConfigs::Configs { configs: vec![$($sys.into_configs(),)*], collective_conditions: Vec::new(), - chained: Chain::No, + chained: Default::default(), } } } @@ -820,7 +821,7 @@ macro_rules! impl_system_set_collection { SystemSetConfigs::Configs { configs: vec![$($set.into_configs(),)*], collective_conditions: Vec::new(), - chained: Chain::No, + chained: Default::default(), } } } diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 8b549a15ea6d3..c99d263e046df 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -18,7 +18,7 @@ use crate::{ component::{ComponentId, Tick}, prelude::{IntoSystemSet, SystemSet}, query::Access, - result::Result, + result::{Error, Result, SystemErrorContext}, schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet}, system::{ScheduleSystem, System, SystemIn}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, @@ -33,6 +33,7 @@ pub(super) trait SystemExecutor: Send + Sync { schedule: &mut SystemSchedule, world: &mut World, skip_systems: Option<&FixedBitSet>, + error_handler: fn(Error, SystemErrorContext), ); fn set_apply_final_deferred(&mut self, value: bool); } @@ -311,7 +312,6 @@ mod __rust_begin_short_backtrace { #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, prelude::{IntoSystemConfigs, IntoSystemSetConfigs, Resource, Schedule, SystemSet}, schedule::ExecutorKind, system::{Commands, Res, WithParamWarnPolicy}, diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index cfa3dc8398431..e78cb666aedd4 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -17,13 +17,12 @@ use crate::{ archetype::ArchetypeComponentId, prelude::Resource, query::Access, + result::{Error, Result, SystemErrorContext}, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, system::ScheduleSystem, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; -use crate as bevy_ecs; - use super::__rust_begin_short_backtrace; /// Borrowed data used by the [`MultiThreadedExecutor`]. @@ -133,6 +132,7 @@ pub struct ExecutorState { struct Context<'scope, 'env, 'sys> { environment: &'env Environment<'env, 'sys>, scope: &'scope Scope<'scope, 'env, ()>, + error_handler: fn(Error, SystemErrorContext), } impl Default for MultiThreadedExecutor { @@ -183,6 +183,7 @@ impl SystemExecutor for MultiThreadedExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, + error_handler: fn(Error, SystemErrorContext), ) { let state = self.state.get_mut().unwrap(); // reset counts @@ -222,7 +223,11 @@ impl SystemExecutor for MultiThreadedExecutor { false, thread_executor, |scope| { - let context = Context { environment, scope }; + let context = Context { + environment, + scope, + error_handler, + }; // The first tick won't need to process finished systems, but we still need to run the loop in // tick_executor() in case a system completes while the first tick still holds the mutex. @@ -603,17 +608,18 @@ impl ExecutorState { // access the world data used by the system. // - `update_archetype_component_access` has been called. unsafe { - // TODO: implement an error-handling API instead of panicking. if let Err(err) = __rust_begin_short_backtrace::run_unsafe( system, context.environment.world_cell, ) { - panic!( - "Encountered an error in system `{}`: {:?}", - &*system.name(), - err + (context.error_handler)( + err, + SystemErrorContext { + name: system.name(), + last_run: system.get_last_run(), + }, ); - }; + } }; })); context.system_completed(system_index, res, system); @@ -657,14 +663,15 @@ impl ExecutorState { // that no other systems currently have access to the world. let world = unsafe { context.environment.world_cell.world_mut() }; let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - // TODO: implement an error-handling API instead of panicking. if let Err(err) = __rust_begin_short_backtrace::run(system, world) { - panic!( - "Encountered an error in system `{}`: {:?}", - &*system.name(), - err + (context.error_handler)( + err, + SystemErrorContext { + name: system.name(), + last_run: system.get_last_run(), + }, ); - }; + } })); context.system_completed(system_index, res, system); }; @@ -796,7 +803,6 @@ impl MainThreadExecutor { #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, prelude::Resource, schedule::{ExecutorKind, IntoSystemConfigs, Schedule}, system::Commands, diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 81f7deab3a302..ad01c324e9ea5 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -8,6 +8,7 @@ use tracing::info_span; use std::eprintln; use crate::{ + result::{Error, SystemErrorContext}, schedule::{ executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, }, @@ -43,6 +44,7 @@ impl SystemExecutor for SimpleExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, + error_handler: fn(Error, SystemErrorContext), ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -104,12 +106,13 @@ impl SystemExecutor for SimpleExecutor { } let f = AssertUnwindSafe(|| { - // TODO: implement an error-handling API instead of panicking. if let Err(err) = __rust_begin_short_backtrace::run(system, world) { - panic!( - "Encountered an error in system `{}`: {:?}", - &*system.name(), - err + error_handler( + err, + SystemErrorContext { + name: system.name(), + last_run: system.get_last_run(), + }, ); } }); diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index 8c5a7e0261bbe..b5c44085d5db3 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -8,6 +8,7 @@ use tracing::info_span; use std::eprintln; use crate::{ + result::{Error, SystemErrorContext}, schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -49,6 +50,7 @@ impl SystemExecutor for SingleThreadedExecutor { schedule: &mut SystemSchedule, world: &mut World, _skip_systems: Option<&FixedBitSet>, + error_handler: fn(Error, SystemErrorContext), ) { // If stepping is enabled, make sure we skip those systems that should // not be run. @@ -112,12 +114,13 @@ impl SystemExecutor for SingleThreadedExecutor { let f = AssertUnwindSafe(|| { if system.is_exclusive() { - // TODO: implement an error-handling API instead of panicking. if let Err(err) = __rust_begin_short_backtrace::run(system, world) { - panic!( - "Encountered an error in system `{}`: {:?}", - &*system.name(), - err + error_handler( + err, + SystemErrorContext { + name: system.name(), + last_run: system.get_last_run(), + }, ); } } else { @@ -127,12 +130,13 @@ impl SystemExecutor for SingleThreadedExecutor { // SAFETY: We have exclusive, single-threaded access to the world and // update_archetype_component_access is being called immediately before this. unsafe { - // TODO: implement an error-handling API instead of panicking. if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) { - panic!( - "Encountered an error in system `{}`: {:?}", - &*system.name(), - err + error_handler( + err, + SystemErrorContext { + name: system.name(), + last_run: system.get_last_run(), + }, ); } }; diff --git a/crates/bevy_ecs/src/schedule/graph/graph_map.rs b/crates/bevy_ecs/src/schedule/graph/graph_map.rs index b6f233ad78f07..b255e55d26df9 100644 --- a/crates/bevy_ecs/src/schedule/graph/graph_map.rs +++ b/crates/bevy_ecs/src/schedule/graph/graph_map.rs @@ -65,7 +65,7 @@ where S: BuildHasher, { /// Create a new `Graph` with estimated capacity. - pub(crate) fn with_capacity(nodes: usize, edges: usize) -> Self + pub fn with_capacity(nodes: usize, edges: usize) -> Self where S: Default, { @@ -89,14 +89,14 @@ where } /// Add node `n` to the graph. - pub(crate) fn add_node(&mut self, n: NodeId) { + pub fn add_node(&mut self, n: NodeId) { self.nodes.entry(n).or_default(); } /// Remove a node `n` from the graph. /// /// Computes in **O(N)** time, due to the removal of edges with other nodes. - pub(crate) fn remove_node(&mut self, n: NodeId) { + pub fn remove_node(&mut self, n: NodeId) { let Some(links) = self.nodes.swap_remove(&n) else { return; }; @@ -166,7 +166,7 @@ where /// Remove edge from `a` to `b` from the graph. /// /// Return `false` if the edge didn't exist. - pub(crate) fn remove_edge(&mut self, a: NodeId, b: NodeId) -> bool { + pub fn remove_edge(&mut self, a: NodeId, b: NodeId) -> bool { let exist1 = self.remove_single_edge(a, b, Outgoing); let exist2 = if a != b { self.remove_single_edge(b, a, Incoming) diff --git a/crates/bevy_ecs/src/schedule/graph/mod.rs b/crates/bevy_ecs/src/schedule/graph/mod.rs index ed94d245734f6..ed25b612ea86a 100644 --- a/crates/bevy_ecs/src/schedule/graph/mod.rs +++ b/crates/bevy_ecs/src/schedule/graph/mod.rs @@ -1,8 +1,13 @@ -use alloc::{vec, vec::Vec}; -use core::fmt::Debug; +use alloc::{boxed::Box, vec, vec::Vec}; +use core::{ + any::{Any, TypeId}, + fmt::Debug, +}; use smallvec::SmallVec; use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_utils::TypeIdMap; + use fixedbitset::FixedBitSet; use crate::schedule::set::*; @@ -21,22 +26,26 @@ pub(crate) enum DependencyKind { Before, /// A node that should be succeeded. After, - /// A node that should be preceded and will **not** automatically insert an instance of `ApplyDeferred` on the edge. - BeforeNoSync, - /// A node that should be succeeded and will **not** automatically insert an instance of `ApplyDeferred` on the edge. - AfterNoSync, } /// An edge to be added to the dependency graph. -#[derive(Clone)] pub(crate) struct Dependency { pub(crate) kind: DependencyKind, pub(crate) set: InternedSystemSet, + pub(crate) options: TypeIdMap>, } impl Dependency { pub fn new(kind: DependencyKind, set: InternedSystemSet) -> Self { - Self { kind, set } + Self { + kind, + set, + options: Default::default(), + } + } + pub fn add_config(mut self, option: T) -> Self { + self.options.insert(TypeId::of::(), Box::new(option)); + self } } @@ -52,7 +61,7 @@ pub(crate) enum Ambiguity { } /// Metadata about how the node fits in the schedule graph -#[derive(Clone, Default)] +#[derive(Default)] pub(crate) struct GraphInfo { /// the sets that the node belongs to (hierarchy) pub(crate) hierarchy: Vec, diff --git a/crates/bevy_ecs/src/schedule/graph/node.rs b/crates/bevy_ecs/src/schedule/graph/node.rs index bf44af73aec5d..e4af143fc7a78 100644 --- a/crates/bevy_ecs/src/schedule/graph/node.rs +++ b/crates/bevy_ecs/src/schedule/graph/node.rs @@ -13,7 +13,7 @@ pub enum NodeId { impl NodeId { /// Returns the internal integer value. - pub(crate) const fn index(&self) -> usize { + pub const fn index(&self) -> usize { match self { NodeId::System(index) | NodeId::Set(index) => *index, } diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index cd565173eaf06..eb48b2c4fe342 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1,25 +1,34 @@ //! Contains APIs for ordering systems and executing them on a [`World`](crate::world::World) +mod auto_insert_apply_deferred; mod condition; mod config; mod executor; -mod graph; +mod pass; mod schedule; mod set; mod stepping; use self::graph::*; pub use self::{condition::*, config::*, executor::*, schedule::*, set::*}; +pub use pass::ScheduleBuildPass; pub use self::graph::NodeId; +/// An implementation of a graph data structure. +pub mod graph; + +/// Included optional schedule build passes. +pub mod passes { + pub use crate::schedule::auto_insert_apply_deferred::*; +} + #[cfg(test)] mod tests { use super::*; use alloc::{string::ToString, vec, vec::Vec}; use core::sync::atomic::{AtomicU32, Ordering}; - pub use crate as bevy_ecs; pub use crate::{ prelude::World, resource::Resource, @@ -720,8 +729,6 @@ mod tests { use alloc::collections::BTreeSet; use super::*; - // Required to make the derive macro behave - use crate as bevy_ecs; use crate::prelude::*; #[derive(Resource)] @@ -1082,7 +1089,7 @@ mod tests { schedule.graph_mut().initialize(&mut world); let _ = schedule.graph_mut().build_schedule( - world.components(), + &mut world, TestSchedule.intern(), &BTreeSet::new(), ); @@ -1131,7 +1138,7 @@ mod tests { let mut world = World::new(); schedule.graph_mut().initialize(&mut world); let _ = schedule.graph_mut().build_schedule( - world.components(), + &mut world, TestSchedule.intern(), &BTreeSet::new(), ); diff --git a/crates/bevy_ecs/src/schedule/pass.rs b/crates/bevy_ecs/src/schedule/pass.rs new file mode 100644 index 0000000000000..20680e04e032c --- /dev/null +++ b/crates/bevy_ecs/src/schedule/pass.rs @@ -0,0 +1,79 @@ +use alloc::{boxed::Box, vec::Vec}; +use core::any::{Any, TypeId}; + +use super::{DiGraph, NodeId, ScheduleBuildError, ScheduleGraph}; +use crate::world::World; +use bevy_utils::TypeIdMap; +use core::fmt::Debug; + +/// A pass for modular modification of the dependency graph. +pub trait ScheduleBuildPass: Send + Sync + Debug + 'static { + /// Custom options for dependencies between sets or systems. + type EdgeOptions: 'static; + + /// Called when a dependency between sets or systems was explicitly added to the graph. + fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>); + + /// Called while flattening the dependency graph. For each `set`, this method is called + /// with the `systems` associated with the set as well as an immutable reference to the current graph. + /// Instead of modifying the graph directly, this method should return an iterator of edges to add to the graph. + fn collapse_set( + &mut self, + set: NodeId, + systems: &[NodeId], + dependency_flattened: &DiGraph, + ) -> impl Iterator; + + /// The implementation will be able to modify the `ScheduleGraph` here. + fn build( + &mut self, + world: &mut World, + graph: &mut ScheduleGraph, + dependency_flattened: &mut DiGraph, + ) -> Result<(), ScheduleBuildError>; +} + +/// Object safe version of [`ScheduleBuildPass`]. +pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug { + fn build( + &mut self, + world: &mut World, + graph: &mut ScheduleGraph, + dependency_flattened: &mut DiGraph, + ) -> Result<(), ScheduleBuildError>; + + fn collapse_set( + &mut self, + set: NodeId, + systems: &[NodeId], + dependency_flattened: &DiGraph, + dependencies_to_add: &mut Vec<(NodeId, NodeId)>, + ); + fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap>); +} +impl ScheduleBuildPassObj for T { + fn build( + &mut self, + world: &mut World, + graph: &mut ScheduleGraph, + dependency_flattened: &mut DiGraph, + ) -> Result<(), ScheduleBuildError> { + self.build(world, graph, dependency_flattened) + } + fn collapse_set( + &mut self, + set: NodeId, + systems: &[NodeId], + dependency_flattened: &DiGraph, + dependencies_to_add: &mut Vec<(NodeId, NodeId)>, + ) { + let iter = self.collapse_set(set, systems, dependency_flattened); + dependencies_to_add.extend(iter); + } + fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap>) { + let option = all_options + .get(&TypeId::of::()) + .and_then(|x| x.downcast_ref::()); + self.add_dependency(from, to, option); + } +} diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 2b30b670c5bab..a613a8745fb9e 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -4,30 +4,33 @@ )] use alloc::{ boxed::Box, - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, format, string::{String, ToString}, vec, vec::Vec, }; use bevy_platform_support::collections::{HashMap, HashSet}; -use bevy_utils::default; -use core::fmt::{Debug, Write}; +use bevy_utils::{default, TypeIdMap}; +use core::{ + any::{Any, TypeId}, + fmt::{Debug, Write}, +}; use disqualified::ShortName; use fixedbitset::FixedBitSet; use log::{error, info, warn}; +use pass::ScheduleBuildPassObj; use thiserror::Error; #[cfg(feature = "trace")] use tracing::info_span; use crate::{ - self as bevy_ecs, component::{ComponentId, Components, Tick}, prelude::Component, resource::Resource, - result::Result, + result::{DefaultSystemErrorHandler, Error, SystemErrorContext}, schedule::*, - system::{IntoSystem, ScheduleSystem}, + system::ScheduleSystem, world::World, }; @@ -46,10 +49,7 @@ pub struct Schedules { impl Schedules { /// Constructs an empty `Schedules` with zero initial capacity. pub fn new() -> Self { - Self { - inner: HashMap::default(), - ignored_scheduling_ambiguities: BTreeSet::new(), - } + Self::default() } /// Inserts a labeled schedule into the map. @@ -225,15 +225,32 @@ fn make_executor(kind: ExecutorKind) -> Box { } /// Chain systems into dependencies -#[derive(PartialEq)] +#[derive(Default)] pub enum Chain { - /// Run nodes in order. If there are deferred parameters in preceding systems a - /// [`ApplyDeferred`] will be added on the edge. - Yes, - /// Run nodes in order. This will not add [`ApplyDeferred`] between nodes. - YesIgnoreDeferred, - /// Nodes are allowed to run in any order. - No, + /// Systems are independent. Nodes are allowed to run in any order. + #[default] + Unchained, + /// Systems are chained. `before -> after` ordering constraints + /// will be added between the successive elements. + Chained(TypeIdMap>), +} +impl Chain { + /// Specify that the systems must be chained. + pub fn set_chained(&mut self) { + if matches!(self, Chain::Unchained) { + *self = Self::Chained(Default::default()); + }; + } + /// Specify that the systems must be chained, and add the specified configuration for + /// all dependencies created between these systems. + pub fn set_chained_with_config(&mut self, config: T) { + self.set_chained(); + if let Chain::Chained(config_map) = self { + config_map.insert(TypeId::of::(), Box::new(config)); + } else { + unreachable!() + }; + } } /// A collection of systems, and the metadata and executor needed to run them @@ -279,6 +296,7 @@ pub struct Schedule { executable: SystemSchedule, executor: Box, executor_initialized: bool, + error_handler: Option, } #[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)] @@ -297,13 +315,17 @@ impl Default for Schedule { impl Schedule { /// Constructs an empty `Schedule`. pub fn new(label: impl ScheduleLabel) -> Self { - Self { + let mut this = Self { label: label.intern(), graph: ScheduleGraph::new(), 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`. @@ -355,12 +377,35 @@ impl Schedule { self } + /// Add a custom build pass to the schedule. + pub fn add_build_pass(&mut self, pass: T) -> &mut Self { + self.graph.passes.insert(TypeId::of::(), Box::new(pass)); + self + } + + /// Remove a custom build pass. + pub fn remove_build_pass(&mut self) { + self.graph.passes.remove(&TypeId::of::()); + } + /// Changes miscellaneous build settings. pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { + if settings.auto_insert_apply_deferred { + self.add_build_pass(passes::AutoInsertApplyDeferredPass::default()); + } else { + self.remove_build_pass::(); + } self.graph.settings = settings; self } + /// Set the error handler to use for systems that return a [`Result`](crate::result::Result). + /// + /// See the [`result` module-level documentation](crate::result) for more information. + pub fn set_error_handler(&mut self, error_handler: fn(Error, SystemErrorContext)) { + self.error_handler = Some(error_handler); + } + /// Returns the schedule's current `ScheduleBuildSettings`. pub fn get_build_settings(&self) -> ScheduleBuildSettings { self.graph.settings.clone() @@ -398,8 +443,11 @@ 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"); + #[cfg(not(feature = "bevy_debug_stepping"))] - self.executor.run(&mut self.executable, world, None); + self.executor + .run(&mut self.executable, world, None, error_handler); #[cfg(feature = "bevy_debug_stepping")] { @@ -408,8 +456,12 @@ impl Schedule { Some(mut stepping) => stepping.skipped_systems(self), }; - self.executor - .run(&mut self.executable, world, skip_systems.as_ref()); + self.executor.run( + &mut self.executable, + world, + skip_systems.as_ref(), + error_handler, + ); } } @@ -425,8 +477,8 @@ impl Schedule { .ignored_scheduling_ambiguities .clone(); self.graph.update_schedule( + world, &mut self.executable, - world.components(), &ignored_ambiguities, self.label, )?; @@ -434,6 +486,10 @@ 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; @@ -580,21 +636,24 @@ impl SystemSetNode { } /// A [`ScheduleSystem`] stored in a [`ScheduleGraph`]. -struct SystemNode { +pub struct SystemNode { inner: Option, } impl SystemNode { + /// Create a new [`SystemNode`] pub fn new(system: ScheduleSystem) -> Self { Self { inner: Some(system), } } + /// Obtain a reference to the [`ScheduleSystem`] represented by this node. pub fn get(&self) -> Option<&ScheduleSystem> { self.inner.as_ref() } + /// Obtain a mutable reference to the [`ScheduleSystem`] represented by this node. pub fn get_mut(&mut self) -> Option<&mut ScheduleSystem> { self.inner.as_mut() } @@ -607,9 +666,9 @@ impl SystemNode { #[derive(Default)] pub struct ScheduleGraph { /// List of systems in the schedule - systems: Vec, + pub systems: Vec, /// List of conditions for each system, in the same order as `systems` - system_conditions: Vec>, + pub system_conditions: Vec>, /// List of system sets in the schedule system_sets: Vec, /// List of conditions for each system set, in the same order as `system_sets` @@ -624,14 +683,14 @@ pub struct ScheduleGraph { /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets) dependency: Dag, ambiguous_with: UnGraph, - ambiguous_with_all: HashSet, + /// Nodes that are allowed to have ambiguous ordering relationship with any other systems. + pub ambiguous_with_all: HashSet, conflicting_systems: Vec<(NodeId, NodeId, Vec)>, anonymous_sets: usize, changed: bool, settings: ScheduleBuildSettings, - /// Dependency edges that will **not** automatically insert an instance of `apply_deferred` on the edge. - no_sync_edges: BTreeSet<(NodeId, NodeId)>, - auto_sync_node_ids: HashMap, + + passes: BTreeMap>, } impl ScheduleGraph { @@ -652,8 +711,7 @@ impl ScheduleGraph { anonymous_sets: 0, changed: false, settings: default(), - no_sync_edges: BTreeSet::new(), - auto_sync_node_ids: HashMap::default(), + passes: default(), } } @@ -806,13 +864,12 @@ impl ScheduleGraph { } => { self.apply_collective_conditions(&mut configs, collective_conditions); - let ignore_deferred = matches!(chained, Chain::YesIgnoreDeferred); - let chained = matches!(chained, Chain::Yes | Chain::YesIgnoreDeferred); + let is_chained = matches!(chained, Chain::Chained(_)); // Densely chained if // * chained and all configs in the chain are densely chained, or // * unchained with a single densely chained config - let mut densely_chained = chained || configs.len() == 1; + let mut densely_chained = is_chained || configs.len() == 1; let mut configs = configs.into_iter(); let mut nodes = Vec::new(); @@ -822,14 +879,14 @@ impl ScheduleGraph { densely_chained, }; }; - let mut previous_result = self.process_configs(first, collect_nodes || chained); + let mut previous_result = self.process_configs(first, collect_nodes || is_chained); densely_chained &= previous_result.densely_chained; for current in configs { - let current_result = self.process_configs(current, collect_nodes || chained); + let current_result = self.process_configs(current, collect_nodes || is_chained); densely_chained &= current_result.densely_chained; - if chained { + if let Chain::Chained(chain_options) = &chained { // if the current result is densely chained, we only need to chain the first node let current_nodes = if current_result.densely_chained { ¤t_result.nodes[..1] @@ -849,8 +906,12 @@ impl ScheduleGraph { .graph .add_edge(*previous_node, *current_node); - if ignore_deferred { - self.no_sync_edges.insert((*previous_node, *current_node)); + for pass in self.passes.values_mut() { + pass.add_dependency( + *previous_node, + *current_node, + chain_options, + ); } } } @@ -973,7 +1034,7 @@ impl ScheduleGraph { id: &NodeId, graph_info: &GraphInfo, ) -> Result<(), ScheduleBuildError> { - for Dependency { kind: _, set } in &graph_info.dependencies { + for Dependency { set, .. } in &graph_info.dependencies { match self.system_set_ids.get(set) { Some(set_id) => { if id == set_id { @@ -1024,23 +1085,18 @@ impl ScheduleGraph { self.dependency.graph.add_node(set); } - for (kind, set) in dependencies + for (kind, set, options) in dependencies .into_iter() - .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set])) + .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options)) { let (lhs, rhs) = match kind { DependencyKind::Before => (id, set), - DependencyKind::BeforeNoSync => { - self.no_sync_edges.insert((id, set)); - (id, set) - } DependencyKind::After => (set, id), - DependencyKind::AfterNoSync => { - self.no_sync_edges.insert((set, id)); - (set, id) - } }; self.dependency.graph.add_edge(lhs, rhs); + for pass in self.passes.values_mut() { + pass.add_dependency(lhs, rhs, &options); + } // ensure set also appears in hierarchy graph self.hierarchy.graph.add_node(set); @@ -1090,7 +1146,7 @@ impl ScheduleGraph { /// - checks for system access conflicts and reports ambiguities pub fn build_schedule( &mut self, - components: &Components, + world: &mut World, schedule_label: InternedScheduleLabel, ignored_ambiguities: &BTreeSet, ) -> Result { @@ -1123,10 +1179,12 @@ impl ScheduleGraph { let mut dependency_flattened = self.get_dependency_flattened(&set_systems); - // modify graph with auto sync points - if self.settings.auto_insert_apply_deferred { - dependency_flattened = self.auto_insert_apply_deferred(&mut dependency_flattened)?; + // modify graph with build passes + let mut passes = core::mem::take(&mut self.passes); + for pass in passes.values_mut() { + pass.build(world, self, &mut dependency_flattened)?; } + self.passes = passes; // topsort let mut dependency_flattened_dag = Dag { @@ -1151,92 +1209,13 @@ impl ScheduleGraph { &ambiguous_with_flattened, ignored_ambiguities, ); - self.optionally_check_conflicts(&conflicting_systems, components, schedule_label)?; + self.optionally_check_conflicts(&conflicting_systems, world.components(), schedule_label)?; self.conflicting_systems = conflicting_systems; // build the schedule Ok(self.build_schedule_inner(dependency_flattened_dag, hier_results.reachable)) } - // modify the graph to have sync nodes for any dependents after a system with deferred system params - fn auto_insert_apply_deferred( - &mut self, - dependency_flattened: &mut DiGraph, - ) -> Result { - let mut sync_point_graph = dependency_flattened.clone(); - let topo = self.topsort_graph(dependency_flattened, ReportCycles::Dependency)?; - - // calculate the number of sync points each sync point is from the beginning of the graph - // use the same sync point if the distance is the same - let mut distances: HashMap> = - HashMap::with_capacity_and_hasher(topo.len(), Default::default()); - for node in &topo { - let add_sync_after = self.systems[node.index()].get().unwrap().has_deferred(); - - for target in dependency_flattened.neighbors_directed(*node, Outgoing) { - let add_sync_on_edge = add_sync_after - && !is_apply_deferred(self.systems[target.index()].get().unwrap()) - && !self.no_sync_edges.contains(&(*node, target)); - - let weight = if add_sync_on_edge { 1 } else { 0 }; - - let distance = distances - .get(&target.index()) - .unwrap_or(&None) - .or(Some(0)) - .map(|distance| { - distance.max( - distances.get(&node.index()).unwrap_or(&None).unwrap_or(0) + weight, - ) - }); - - distances.insert(target.index(), distance); - - if add_sync_on_edge { - let sync_point = self.get_sync_point(distances[&target.index()].unwrap()); - sync_point_graph.add_edge(*node, sync_point); - sync_point_graph.add_edge(sync_point, target); - - // edge is now redundant - sync_point_graph.remove_edge(*node, target); - } - } - } - - Ok(sync_point_graph) - } - - /// add an [`ApplyDeferred`] system with no config - fn add_auto_sync(&mut self) -> NodeId { - let id = NodeId::System(self.systems.len()); - - self.systems - .push(SystemNode::new(Box::new(IntoSystem::into_system( - ApplyDeferred, - )))); - self.system_conditions.push(Vec::new()); - - // ignore ambiguities with auto sync points - // They aren't under user control, so no one should know or care. - self.ambiguous_with_all.insert(id); - - id - } - - /// Returns the `NodeId` of the cached auto sync point. Will create - /// a new one if needed. - fn get_sync_point(&mut self, distance: u32) -> NodeId { - self.auto_sync_node_ids - .get(&distance) - .copied() - .or_else(|| { - let node_id = self.add_auto_sync(); - self.auto_sync_node_ids.insert(distance, node_id); - Some(node_id) - }) - .unwrap() - } - /// Return a map from system set `NodeId` to a list of system `NodeId`s that are included in the set. /// Also return a map from system set `NodeId` to a `FixedBitSet` of system `NodeId`s that are included in the set, /// where the bitset order is the same as `self.systems` @@ -1284,34 +1263,25 @@ impl ScheduleGraph { let mut dependency_flattened = self.dependency.graph.clone(); let mut temp = Vec::new(); for (&set, systems) in set_systems { + for pass in self.passes.values_mut() { + pass.collapse_set(set, systems, &dependency_flattened, &mut temp); + } if systems.is_empty() { // collapse dependencies for empty sets for a in dependency_flattened.neighbors_directed(set, Incoming) { for b in dependency_flattened.neighbors_directed(set, Outgoing) { - if self.no_sync_edges.contains(&(a, set)) - && self.no_sync_edges.contains(&(set, b)) - { - self.no_sync_edges.insert((a, b)); - } - temp.push((a, b)); } } } else { for a in dependency_flattened.neighbors_directed(set, Incoming) { for &sys in systems { - if self.no_sync_edges.contains(&(a, set)) { - self.no_sync_edges.insert((a, sys)); - } temp.push((a, sys)); } } for b in dependency_flattened.neighbors_directed(set, Outgoing) { for &sys in systems { - if self.no_sync_edges.contains(&(set, b)) { - self.no_sync_edges.insert((sys, b)); - } temp.push((sys, b)); } } @@ -1506,8 +1476,8 @@ impl ScheduleGraph { /// Updates the `SystemSchedule` from the `ScheduleGraph`. fn update_schedule( &mut self, + world: &mut World, schedule: &mut SystemSchedule, - components: &Components, ignored_ambiguities: &BTreeSet, schedule_label: InternedScheduleLabel, ) -> Result<(), ScheduleBuildError> { @@ -1534,7 +1504,7 @@ impl ScheduleGraph { self.system_set_conditions[id.index()] = conditions; } - *schedule = self.build_schedule(components, schedule_label, ignored_ambiguities)?; + *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?; // move systems into new schedule for &id in &schedule.system_ids { @@ -1583,8 +1553,10 @@ impl ProcessNodeConfig for InternedSystemSet { } /// Used to select the appropriate reporting function. -enum ReportCycles { +pub enum ReportCycles { + /// When sets contain themselves Hierarchy, + /// When the graph is no longer a DAG Dependency, } @@ -1701,7 +1673,7 @@ impl ScheduleGraph { /// # Errors /// /// If the graph contain cycles, then an error is returned. - fn topsort_graph( + pub fn topsort_graph( &self, graph: &DiGraph, report: ReportCycles, @@ -2064,7 +2036,6 @@ mod tests { use bevy_ecs_macros::ScheduleLabel; use crate::{ - self as bevy_ecs, prelude::{Res, Resource}, schedule::{ tests::ResMut, IntoSystemConfigs, IntoSystemSetConfigs, Schedule, diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 010e9b5303848..896c7ed050ecc 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -225,7 +225,7 @@ mod tests { #[test] fn test_schedule_label() { - use crate::{self as bevy_ecs, world::World}; + use crate::world::World; #[derive(Resource)] struct Flag(bool); @@ -257,8 +257,6 @@ mod tests { #[test] fn test_derive_schedule_label() { - use crate::{self as bevy_ecs}; - #[derive(ScheduleLabel, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct UnitLabel; @@ -359,8 +357,6 @@ mod tests { #[test] fn test_derive_system_set() { - use crate::{self as bevy_ecs}; - #[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct UnitSet; diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index 2ba41c60b8cd5..f26da4cad370a 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -17,8 +17,6 @@ use log::error; #[cfg(test)] use log::debug; -use crate as bevy_ecs; - #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] enum Action { /// Stepping is disabled; run all systems @@ -831,8 +829,6 @@ mod tests { use alloc::{format, vec}; use std::println; - pub use crate as bevy_ecs; - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] struct TestSchedule; diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs new file mode 100644 index 0000000000000..2fb04c4c7b293 --- /dev/null +++ b/crates/bevy_ecs/src/spawn.rs @@ -0,0 +1,351 @@ +//! Entity spawning abstractions, largely focused on spawning related hierarchies of entities. See [`related`](crate::related) and [`SpawnRelated`] +//! for the best entry points into these APIs and examples of how to use them. + +use crate::{ + bundle::{Bundle, BundleEffect, DynamicBundle}, + entity::Entity, + relationship::{RelatedSpawner, Relationship, RelationshipTarget}, + world::{EntityWorldMut, World}, +}; +use core::marker::PhantomData; +use variadics_please::all_tuples; + +/// A wrapper over a [`Bundle`] indicating that an entity should be spawned with that [`Bundle`]. +/// This is intended to be used for hierarchical spawning via traits like [`SpawnableList`] and [`SpawnRelated`]. +/// +/// Also see the [`children`](crate::children) and [`related`](crate::related) macros that abstract over the [`Spawn`] API. +/// +/// ``` +/// # use bevy_ecs::hierarchy::Children; +/// # use bevy_ecs::spawn::{Spawn, SpawnRelated}; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::world::World; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// Children::spawn(( +/// Spawn(Name::new("Child1")), +/// Spawn(( +/// Name::new("Child2"), +/// Children::spawn(Spawn(Name::new("Grandchild"))), +/// )) +/// )), +/// )); +/// ``` +pub struct Spawn(pub B); + +/// A spawn-able list of changes to a given [`World`] and relative to a given [`Entity`]. This is generally used +/// for spawning "related" entities, such as children. +pub trait SpawnableList { + /// Spawn this list of changes in a given [`World`] and relative to a given [`Entity`]. This is generally used + /// for spawning "related" entities, such as children. + fn spawn(self, world: &mut World, entity: Entity); + /// Returns a size hint, which is used to reserve space for this list in a [`RelationshipTarget`]. This should be + /// less than or equal to the actual size of the list. When in doubt, just use 0. + fn size_hint(&self) -> usize; +} + +impl SpawnableList for Spawn { + fn spawn(self, world: &mut World, entity: Entity) { + world.spawn((R::from(entity), self.0)); + } + + fn size_hint(&self) -> usize { + 1 + } +} + +/// A [`SpawnableList`] that spawns entities using an iterator of a given [`Bundle`]: +/// +/// ``` +/// # use bevy_ecs::hierarchy::Children; +/// # use bevy_ecs::spawn::{Spawn, SpawnIter, SpawnRelated}; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::world::World; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// Children::spawn(( +/// Spawn(Name::new("Child1")), +/// SpawnIter(["Child2", "Child3"].into_iter().map(Name::new)), +/// )), +/// )); +/// ``` +pub struct SpawnIter(pub I); + +impl + Send + Sync + 'static, B: Bundle> SpawnableList + for SpawnIter +{ + fn spawn(self, world: &mut World, entity: Entity) { + for bundle in self.0 { + world.spawn((R::from(entity), bundle)); + } + } + + fn size_hint(&self) -> usize { + self.0.size_hint().0 + } +} + +/// A [`SpawnableList`] that spawns entities using a [`FnOnce`] with a [`RelatedSpawner`] as an argument: +/// +/// ``` +/// # use bevy_ecs::hierarchy::{Children, ChildOf}; +/// # use bevy_ecs::spawn::{Spawn, SpawnWith, SpawnRelated}; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::relationship::RelatedSpawner; +/// # use bevy_ecs::world::World; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// Children::spawn(( +/// Spawn(Name::new("Child1")), +/// SpawnWith(|parent: &mut RelatedSpawner| { +/// parent.spawn(Name::new("Child2")); +/// parent.spawn(Name::new("Child3")); +/// }), +/// )), +/// )); +/// ``` +pub struct SpawnWith(pub F); + +impl) + Send + Sync + 'static> SpawnableList + for SpawnWith +{ + fn spawn(self, world: &mut World, entity: Entity) { + world.entity_mut(entity).with_related(self.0); + } + + fn size_hint(&self) -> usize { + 1 + } +} + +macro_rules! spawnable_list_impl { + ($($list: ident),*) => { + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + impl),*> SpawnableList for ($($list,)*) { + fn spawn(self, _world: &mut World, _entity: Entity) { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($list,)*) = self; + $($list.spawn(_world, _entity);)* + } + + fn size_hint(&self) -> usize { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($list,)*) = self; + 0 $(+ $list.size_hint())* + } + } + } +} + +all_tuples!(spawnable_list_impl, 0, 12, P); + +/// A [`Bundle`] that: +/// 1. Contains a [`RelationshipTarget`] component (associated with the given [`Relationship`]). This reserves space for the [`SpawnableList`]. +/// 2. Spawns a [`SpawnableList`] of related entities with a given [`Relationship`]. +/// +/// This is intended to be created using [`SpawnRelated`]. +pub struct SpawnRelatedBundle> { + list: L, + marker: PhantomData, +} + +impl> BundleEffect for SpawnRelatedBundle { + fn apply(self, entity: &mut EntityWorldMut) { + let id = entity.id(); + entity.world_scope(|world: &mut World| { + self.list.spawn(world, id); + }); + } +} + +// SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. +unsafe impl + Send + Sync + 'static> Bundle + for SpawnRelatedBundle +{ + fn component_ids( + components: &mut crate::component::Components, + ids: &mut impl FnMut(crate::component::ComponentId), + ) { + ::component_ids(components, ids); + } + + fn get_component_ids( + components: &crate::component::Components, + ids: &mut impl FnMut(Option), + ) { + ::get_component_ids(components, ids); + } + + fn register_required_components( + components: &mut crate::component::Components, + required_components: &mut crate::component::RequiredComponents, + ) { + ::register_required_components( + components, + required_components, + ); + } +} +impl> DynamicBundle for SpawnRelatedBundle { + type Effect = Self; + + fn get_components( + self, + func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>), + ) -> Self::Effect { + ::with_capacity(self.list.size_hint()) + .get_components(func); + self + } +} + +/// A [`Bundle`] that: +/// 1. Contains a [`RelationshipTarget`] component (associated with the given [`Relationship`]). This reserves space for a single entity. +/// 2. Spawns a single related entity containing the given `B` [`Bundle`] and the given [`Relationship`]. +/// +/// This is intended to be created using [`SpawnRelated`]. +pub struct SpawnOneRelated { + bundle: B, + marker: PhantomData, +} + +impl BundleEffect for SpawnOneRelated { + fn apply(self, entity: &mut EntityWorldMut) { + entity.with_related::(|s| { + s.spawn(self.bundle); + }); + } +} + +impl DynamicBundle for SpawnOneRelated { + type Effect = Self; + + fn get_components( + self, + func: &mut impl FnMut(crate::component::StorageType, bevy_ptr::OwningPtr<'_>), + ) -> Self::Effect { + ::with_capacity(1).get_components(func); + self + } +} + +// SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. +unsafe impl Bundle for SpawnOneRelated { + fn component_ids( + components: &mut crate::component::Components, + ids: &mut impl FnMut(crate::component::ComponentId), + ) { + ::component_ids(components, ids); + } + + fn get_component_ids( + components: &crate::component::Components, + ids: &mut impl FnMut(Option), + ) { + ::get_component_ids(components, ids); + } + + fn register_required_components( + components: &mut crate::component::Components, + required_components: &mut crate::component::RequiredComponents, + ) { + ::register_required_components( + components, + required_components, + ); + } +} + +/// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that: +/// +/// 1. Contains the [`RelationshipTarget`] component, pre-allocated with the necessary space for spawned entities. +/// 2. Spawns an entity (or a list of entities) that relate to the entity the [`Bundle`] is added to via the [`RelationshipTarget::Relationship`]. +pub trait SpawnRelated: RelationshipTarget { + /// Returns a [`Bundle`] containing this [`RelationshipTarget`] component. It also spawns a [`SpawnableList`] of entities, each related to the bundle's entity + /// via [`RelationshipTarget::Relationship`]. The [`RelationshipTarget`] (when possible) will pre-allocate space for the related entities. + /// + /// See [`Spawn`], [`SpawnIter`], and [`SpawnWith`] for usage examples. + fn spawn>( + list: L, + ) -> SpawnRelatedBundle; + + /// Returns a [`Bundle`] containing this [`RelationshipTarget`] component. It also spawns a single entity containing [`Bundle`] that is related to the bundle's entity + /// via [`RelationshipTarget::Relationship`]. + /// + /// ``` + /// # use bevy_ecs::hierarchy::Children; + /// # use bevy_ecs::spawn::SpawnRelated; + /// # use bevy_ecs::name::Name; + /// # use bevy_ecs::world::World; + /// let mut world = World::new(); + /// world.spawn(( + /// Name::new("Root"), + /// Children::spawn_one(Name::new("Child")), + /// )); + /// ``` + fn spawn_one(bundle: B) -> SpawnOneRelated; +} + +impl SpawnRelated for T { + fn spawn>( + list: L, + ) -> SpawnRelatedBundle { + SpawnRelatedBundle { + list, + marker: PhantomData, + } + } + + fn spawn_one(bundle: B) -> SpawnOneRelated { + SpawnOneRelated { + bundle, + marker: PhantomData, + } + } +} + +/// Returns a [`SpawnRelatedBundle`] that will insert the given [`RelationshipTarget`], spawn a [`SpawnableList`] of entities with given bundles that +/// relate to the [`RelationshipTarget`] entity via the [`RelationshipTarget::Relationship`] component, and reserve space in the [`RelationshipTarget`] for each spawned entity. +/// +/// The first argument is the [`RelationshipTarget`] type. Any additional arguments will be interpreted as bundles to be spawned. +/// +/// Also see [`children`](crate::children) for a [`Children`](crate::hierarchy::Children)-specific equivalent. +/// +/// ``` +/// # use bevy_ecs::hierarchy::Children; +/// # use bevy_ecs::name::Name; +/// # use bevy_ecs::world::World; +/// # use bevy_ecs::related; +/// # use bevy_ecs::spawn::{Spawn, SpawnRelated}; +/// let mut world = World::new(); +/// world.spawn(( +/// Name::new("Root"), +/// related!(Children[ +/// Name::new("Child1"), +/// ( +/// Name::new("Child2"), +/// related!(Children[ +/// Name::new("Grandchild"), +/// ]) +/// ) +/// ]) +/// )); +/// ``` +#[macro_export] +macro_rules! related { + ($relationship_target:ty [$($child:expr),*$(,)?]) => { + <$relationship_target>::spawn(($($crate::spawn::Spawn($child)),*)) + }; +} diff --git a/crates/bevy_ecs/src/storage/blob_array.rs b/crates/bevy_ecs/src/storage/blob_array.rs index 86315386a8d1f..d10f3f1459f2d 100644 --- a/crates/bevy_ecs/src/storage/blob_array.rs +++ b/crates/bevy_ecs/src/storage/blob_array.rs @@ -256,7 +256,7 @@ impl BlobArray { new_capacity: NonZeroUsize, ) { #[cfg(debug_assertions)] - debug_assert_eq!(self.capacity, current_capacity.into()); + debug_assert_eq!(self.capacity, current_capacity.get()); if !self.is_zst() { // SAFETY: `new_capacity` can't overflow usize let new_layout = @@ -479,7 +479,6 @@ impl BlobArray { #[cfg(test)] mod tests { - use crate as bevy_ecs; use bevy_ecs::prelude::*; #[derive(Component)] diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index 51a3d49e3c08d..971cf80fa7820 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -498,7 +498,6 @@ const fn padding_needed_for(layout: &Layout, align: usize) -> usize { #[cfg(test)] mod tests { use super::BlobVec; - use crate as bevy_ecs; // required for derive macros use crate::{component::Component, ptr::OwningPtr, world::World}; use alloc::{ rc::Rc, diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index eaeff97a8cccb..2a5a5f184e649 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -31,10 +31,13 @@ pub use resource::*; pub use sparse_set::*; pub use table::*; +use crate::component::{ComponentInfo, StorageType}; + /// The raw data stores of a [`World`](crate::world::World) #[derive(Default)] pub struct Storages { /// Backing storage for [`SparseSet`] components. + /// Note that sparse sets are only present for components that have been spawned or have had a relevant bundle registered. pub sparse_sets: SparseSets, /// Backing storage for [`Table`] components. pub tables: Tables, @@ -43,3 +46,17 @@ pub struct Storages { /// Backing storage for `!Send` resources. pub non_send_resources: Resources, } + +impl Storages { + /// ensures that the component has its necessary storage initialize. + pub fn prepare_component(&mut self, component: &ComponentInfo) { + match component.storage_type() { + StorageType::Table => { + // table needs no preparation + } + StorageType::SparseSet => { + self.sparse_sets.get_or_insert(component); + } + } + } +} diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index ac9bbfa54d76d..078acade7d9ef 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -1,14 +1,12 @@ use crate::{ archetype::ArchetypeComponentId, - change_detection::{MaybeLocation, MaybeUnsafeCellLocation, MutUntyped, TicksMut}, + change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick, TickCells}, storage::{blob_vec::BlobVec, SparseSet}, }; use alloc::string::String; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; -#[cfg(feature = "track_location")] -use core::panic::Location; -use core::{cell::UnsafeCell, mem::ManuallyDrop}; +use core::{cell::UnsafeCell, mem::ManuallyDrop, panic::Location}; #[cfg(feature = "std")] use std::thread::ThreadId; @@ -30,8 +28,7 @@ pub struct ResourceData { id: ArchetypeComponentId, #[cfg(feature = "std")] origin_thread_id: Option, - #[cfg(feature = "track_location")] - changed_by: UnsafeCell<&'static Location<'static>>, + changed_by: MaybeLocation>>, } impl Drop for ResourceData { @@ -136,7 +133,11 @@ impl ResourceData { #[inline] pub(crate) fn get_with_ticks( &self, - ) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { + ) -> Option<( + Ptr<'_>, + TickCells<'_>, + MaybeLocation<&UnsafeCell<&'static Location<'static>>>, + )> { self.is_present().then(|| { self.validate_access(); ( @@ -146,10 +147,7 @@ impl ResourceData { added: &self.added_ticks, changed: &self.changed_ticks, }, - #[cfg(feature = "track_location")] - &self.changed_by, - #[cfg(not(feature = "track_location"))] - (), + self.changed_by.as_ref(), ) }) } @@ -160,15 +158,14 @@ impl ResourceData { /// If `SEND` is false, this will panic if a value is present and is not accessed from the /// original thread it was inserted in. pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option> { - let (ptr, ticks, _caller) = self.get_with_ticks()?; + let (ptr, ticks, caller) = self.get_with_ticks()?; Some(MutUntyped { // SAFETY: We have exclusive access to the underlying storage. value: unsafe { ptr.assert_unique() }, // SAFETY: We have exclusive access to the underlying storage. ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) }, - #[cfg(feature = "track_location")] // SAFETY: We have exclusive access to the underlying storage. - changed_by: unsafe { _caller.deref_mut() }, + changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, }) } @@ -186,7 +183,7 @@ impl ResourceData { &mut self, value: OwningPtr<'_>, change_tick: Tick, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) { if self.is_present() { self.validate_access(); @@ -205,10 +202,11 @@ impl ResourceData { *self.added_ticks.deref_mut() = change_tick; } *self.changed_ticks.deref_mut() = change_tick; - #[cfg(feature = "track_location")] - { - *self.changed_by.deref_mut() = caller; - } + + self.changed_by + .as_ref() + .map(|changed_by| changed_by.deref_mut()) + .assign(caller); } /// Inserts a value into the resource with a pre-existing change tick. If a @@ -225,7 +223,7 @@ impl ResourceData { &mut self, value: OwningPtr<'_>, change_ticks: ComponentTicks, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) { if self.is_present() { self.validate_access(); @@ -244,10 +242,10 @@ impl ResourceData { } *self.added_ticks.deref_mut() = change_ticks.added; *self.changed_ticks.deref_mut() = change_ticks.changed; - #[cfg(feature = "track_location")] - { - *self.changed_by.deref_mut() = caller; - } + self.changed_by + .as_ref() + .map(|changed_by| changed_by.deref_mut()) + .assign(caller); } /// Removes a value from the resource, if present. @@ -267,11 +265,11 @@ impl ResourceData { // SAFETY: We've already validated that the row is present. let res = unsafe { self.data.swap_remove_and_forget_unchecked(Self::ROW) }; - // SAFETY: This function is being called through an exclusive mutable reference to Self - #[cfg(feature = "track_location")] - let caller = unsafe { *self.changed_by.deref_mut() }; - #[cfg(not(feature = "track_location"))] - let caller = (); + let caller = self + .changed_by + .as_ref() + // SAFETY: This function is being called through an exclusive mutable reference to Self + .map(|changed_by| unsafe { *changed_by.deref_mut() }); // SAFETY: This function is being called through an exclusive mutable reference to Self, which // makes it sound to read these ticks. @@ -392,8 +390,7 @@ impl Resources { id: f(), #[cfg(feature = "std")] origin_thread_id: None, - #[cfg(feature = "track_location")] - changed_by: UnsafeCell::new(Location::caller()) + 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 518333fa272d8..671cd7c161d67 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -1,14 +1,12 @@ use crate::{ - change_detection::MaybeUnsafeCellLocation, + change_detection::MaybeLocation, component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells}, entity::Entity, storage::{Column, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; use bevy_ptr::{OwningPtr, Ptr}; -#[cfg(feature = "track_location")] -use core::panic::Location; -use core::{cell::UnsafeCell, hash::Hash, marker::PhantomData}; +use core::{cell::UnsafeCell, hash::Hash, marker::PhantomData, panic::Location}; use nonmax::NonMaxUsize; type EntityIndex = u32; @@ -170,26 +168,16 @@ impl ComponentSparseSet { entity: Entity, value: OwningPtr<'_>, change_tick: Tick, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if let Some(&dense_index) = self.sparse.get(entity.index()) { #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index.as_usize()]); - self.dense.replace( - dense_index, - value, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + self.dense.replace(dense_index, value, change_tick, caller); } else { let dense_index = self.dense.len(); - self.dense.push( - value, - ComponentTicks::new(change_tick), - #[cfg(feature = "track_location")] - caller, - ); + self.dense + .push(value, ComponentTicks::new(change_tick), caller); self.sparse .insert(entity.index(), TableRow::from_usize(dense_index)); #[cfg(debug_assertions)] @@ -238,7 +226,11 @@ impl ComponentSparseSet { pub fn get_with_ticks( &self, entity: Entity, - ) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { + ) -> Option<( + Ptr<'_>, + TickCells<'_>, + MaybeLocation<&UnsafeCell<&'static Location<'static>>>, + )> { let dense_index = *self.sparse.get(entity.index())?; #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index.as_usize()]); @@ -250,10 +242,7 @@ impl ComponentSparseSet { added: self.dense.get_added_tick_unchecked(dense_index), changed: self.dense.get_changed_tick_unchecked(dense_index), }, - #[cfg(feature = "track_location")] self.dense.get_changed_by_unchecked(dense_index), - #[cfg(not(feature = "track_location"))] - (), )) } } @@ -298,16 +287,17 @@ impl ComponentSparseSet { /// /// Returns `None` if `entity` does not have a component in the sparse set. #[inline] - #[cfg(feature = "track_location")] pub fn get_changed_by( &self, entity: Entity, - ) -> Option<&UnsafeCell<&'static Location<'static>>> { - let dense_index = *self.sparse.get(entity.index())?; - #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index.as_usize()]); - // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { Some(self.dense.get_changed_by_unchecked(dense_index)) } + ) -> MaybeLocation>>> { + MaybeLocation::new_with_flattened(|| { + let dense_index = *self.sparse.get(entity.index())?; + #[cfg(debug_assertions)] + assert_eq!(entity, self.entities[dense_index.as_usize()]); + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { Some(self.dense.get_changed_by_unchecked(dense_index)) } + }) } /// Removes the `entity` from this sparse set and returns a pointer to the associated value (if @@ -616,7 +606,7 @@ impl SparseSets { self.sets.iter().map(|(id, data)| (*id, data)) } - /// Gets a reference to the [`ComponentSparseSet`] of a [`ComponentId`]. + /// Gets a reference to the [`ComponentSparseSet`] of a [`ComponentId`]. This may be `None` if the component has never been spawned. #[inline] pub fn get(&self, component_id: ComponentId) -> Option<&ComponentSparseSet> { self.sets.get(component_id) @@ -638,7 +628,7 @@ impl SparseSets { self.sets.get_mut(component_info.id()).unwrap() } - /// Gets a mutable reference to the [`ComponentSparseSet`] of a [`ComponentId`]. + /// Gets a mutable reference to the [`ComponentSparseSet`] of a [`ComponentId`]. This may be `None` if the component has never been spawned. pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ComponentSparseSet> { self.sets.get_mut(component_id) } @@ -661,7 +651,6 @@ impl SparseSets { mod tests { use super::SparseSets; use crate::{ - self as bevy_ecs, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo}, entity::Entity, storage::SparseSet, diff --git a/crates/bevy_ecs/src/storage/table/column.rs b/crates/bevy_ecs/src/storage/table/column.rs index 4054b5c15fd54..d4690d264cb32 100644 --- a/crates/bevy_ecs/src/storage/table/column.rs +++ b/crates/bevy_ecs/src/storage/table/column.rs @@ -1,10 +1,12 @@ use super::*; use crate::{ + change_detection::MaybeLocation, component::TickCells, storage::{blob_array::BlobArray, thin_array_ptr::ThinArrayPtr}, }; use alloc::vec::Vec; use bevy_ptr::PtrMut; +use core::panic::Location; /// Very similar to a normal [`Column`], but with the capacities and lengths cut out for performance reasons. /// @@ -17,8 +19,7 @@ pub struct ThinColumn { pub(super) data: BlobArray, pub(super) added_ticks: ThinArrayPtr>, pub(super) changed_ticks: ThinArrayPtr>, - #[cfg(feature = "track_location")] - pub(super) changed_by: ThinArrayPtr>>, + pub(super) changed_by: MaybeLocation>>>, } impl ThinColumn { @@ -31,8 +32,7 @@ impl ThinColumn { }, added_ticks: ThinArrayPtr::with_capacity(capacity), changed_ticks: ThinArrayPtr::with_capacity(capacity), - #[cfg(feature = "track_location")] - changed_by: ThinArrayPtr::with_capacity(capacity), + changed_by: MaybeLocation::new_with(|| ThinArrayPtr::with_capacity(capacity)), } } @@ -54,9 +54,9 @@ impl ThinColumn { .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); self.changed_ticks .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); - #[cfg(feature = "track_location")] - self.changed_by - .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + self.changed_by.as_mut().map(|changed_by| { + changed_by.swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + }); } /// Swap-remove and drop the removed element. @@ -76,9 +76,9 @@ impl ThinColumn { .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); self.changed_ticks .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); - #[cfg(feature = "track_location")] - self.changed_by - .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + self.changed_by.as_mut().map(|changed_by| { + changed_by.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + }); } /// Swap-remove and forget the removed element. @@ -99,9 +99,9 @@ impl ThinColumn { .swap_remove_unchecked(row.as_usize(), last_element_index); self.changed_ticks .swap_remove_unchecked(row.as_usize(), last_element_index); - #[cfg(feature = "track_location")] self.changed_by - .swap_remove_unchecked(row.as_usize(), last_element_index); + .as_mut() + .map(|changed_by| changed_by.swap_remove_unchecked(row.as_usize(), last_element_index)); } /// Call [`realloc`](std::alloc::realloc) to expand / shrink the memory allocation for this [`ThinColumn`] @@ -117,8 +117,9 @@ impl ThinColumn { self.data.realloc(current_capacity, new_capacity); self.added_ticks.realloc(current_capacity, new_capacity); self.changed_ticks.realloc(current_capacity, new_capacity); - #[cfg(feature = "track_location")] - self.changed_by.realloc(current_capacity, new_capacity); + self.changed_by + .as_mut() + .map(|changed_by| changed_by.realloc(current_capacity, new_capacity)); } /// Call [`alloc`](std::alloc::alloc) to allocate memory for this [`ThinColumn`] @@ -127,8 +128,9 @@ impl ThinColumn { self.data.alloc(new_capacity); self.added_ticks.alloc(new_capacity); self.changed_ticks.alloc(new_capacity); - #[cfg(feature = "track_location")] - self.changed_by.alloc(new_capacity); + self.changed_by + .as_mut() + .map(|changed_by| changed_by.alloc(new_capacity)); } /// Writes component data to the column at the given row. @@ -144,7 +146,7 @@ impl ThinColumn { row: TableRow, data: OwningPtr<'_>, tick: Tick, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { self.data.initialize_unchecked(row.as_usize(), data); *self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick; @@ -152,10 +154,10 @@ impl ThinColumn { .changed_ticks .get_unchecked_mut(row.as_usize()) .get_mut() = tick; - #[cfg(feature = "track_location")] - { - *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; - } + self.changed_by + .as_mut() + .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .assign(caller); } /// Writes component data to the column at given row. Assumes the slot is initialized, drops the previous value. @@ -169,17 +171,17 @@ impl ThinColumn { row: TableRow, data: OwningPtr<'_>, change_tick: Tick, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { self.data.replace_unchecked(row.as_usize(), data); *self .changed_ticks .get_unchecked_mut(row.as_usize()) .get_mut() = change_tick; - #[cfg(feature = "track_location")] - { - *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; - } + self.changed_by + .as_mut() + .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .assign(caller); } /// Removes the element from `other` at `src_row` and inserts it @@ -218,13 +220,13 @@ impl ThinColumn { .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); self.changed_ticks .initialize_unchecked(dst_row.as_usize(), changed_tick); - #[cfg(feature = "track_location")] - let changed_by = other - .changed_by - .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); - #[cfg(feature = "track_location")] - self.changed_by - .initialize_unchecked(dst_row.as_usize(), changed_by); + 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); + }, + ); } /// Call [`Tick::check_tick`] on all of the ticks stored in this column. @@ -258,8 +260,9 @@ impl ThinColumn { self.added_ticks.clear_elements(len); self.changed_ticks.clear_elements(len); self.data.clear(len); - #[cfg(feature = "track_location")] - self.changed_by.clear_elements(len); + self.changed_by + .as_mut() + .map(|changed_by| changed_by.clear_elements(len)); } /// Because this method needs parameters, it can't be the implementation of the `Drop` trait. @@ -273,8 +276,9 @@ impl ThinColumn { self.added_ticks.drop(cap, len); self.changed_ticks.drop(cap, len); self.data.drop(cap, len); - #[cfg(feature = "track_location")] - self.changed_by.drop(cap, len); + self.changed_by + .as_mut() + .map(|changed_by| changed_by.drop(cap, len)); } /// Drops the last component in this column. @@ -285,8 +289,9 @@ impl ThinColumn { pub(crate) unsafe fn drop_last_component(&mut self, last_element_index: usize) { core::ptr::drop_in_place(self.added_ticks.get_unchecked_raw(last_element_index)); core::ptr::drop_in_place(self.changed_ticks.get_unchecked_raw(last_element_index)); - #[cfg(feature = "track_location")] - core::ptr::drop_in_place(self.changed_by.get_unchecked_raw(last_element_index)); + self.changed_by.as_mut().map(|changed_by| { + core::ptr::drop_in_place(changed_by.get_unchecked_raw(last_element_index)); + }); self.data.drop_last_element(last_element_index); } @@ -319,12 +324,13 @@ impl ThinColumn { /// /// # Safety /// - `len` must match the actual length of this column (number of elements stored) - #[cfg(feature = "track_location")] pub unsafe fn get_changed_by_slice( &self, len: usize, - ) -> &[UnsafeCell<&'static Location<'static>>] { - self.changed_by.as_slice(len) + ) -> MaybeLocation<&[UnsafeCell<&'static Location<'static>>]> { + self.changed_by + .as_ref() + .map(|changed_by| changed_by.as_slice(len)) } } @@ -343,8 +349,7 @@ pub struct Column { pub(super) data: BlobVec, pub(super) added_ticks: Vec>, pub(super) changed_ticks: Vec>, - #[cfg(feature = "track_location")] - changed_by: Vec>>, + changed_by: MaybeLocation>>>, } impl Column { @@ -356,8 +361,7 @@ impl Column { data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, added_ticks: Vec::with_capacity(capacity), changed_ticks: Vec::with_capacity(capacity), - #[cfg(feature = "track_location")] - changed_by: Vec::with_capacity(capacity), + changed_by: MaybeLocation::new_with(|| Vec::with_capacity(capacity)), } } @@ -378,7 +382,7 @@ impl Column { row: TableRow, data: OwningPtr<'_>, change_tick: Tick, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { debug_assert!(row.as_usize() < self.len()); self.data.replace_unchecked(row.as_usize(), data); @@ -386,10 +390,10 @@ impl Column { .changed_ticks .get_unchecked_mut(row.as_usize()) .get_mut() = change_tick; - #[cfg(feature = "track_location")] - { - *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; - } + self.changed_by + .as_mut() + .map(|changed_by| changed_by.get_unchecked_mut(row.as_usize()).get_mut()) + .assign(caller); } /// Gets the current number of elements stored in the column. @@ -418,8 +422,9 @@ impl Column { 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()); - #[cfg(feature = "track_location")] - self.changed_by.swap_remove(row.as_usize()); + self.changed_by + .as_mut() + .map(|changed_by| changed_by.swap_remove(row.as_usize())); } /// Removes an element from the [`Column`] and returns it and its change detection ticks. @@ -442,10 +447,10 @@ impl Column { 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(); - #[cfg(feature = "track_location")] - let caller = self.changed_by.swap_remove(row.as_usize()).into_inner(); - #[cfg(not(feature = "track_location"))] - let caller = (); + let caller = self + .changed_by + .as_mut() + .map(|changed_by| changed_by.swap_remove(row.as_usize()).into_inner()); (data, ComponentTicks { added, changed }, caller) } @@ -457,13 +462,15 @@ impl Column { &mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { self.data.push(ptr); self.added_ticks.push(UnsafeCell::new(ticks.added)); self.changed_ticks.push(UnsafeCell::new(ticks.changed)); - #[cfg(feature = "track_location")] - self.changed_by.push(UnsafeCell::new(caller)); + self.changed_by + .as_mut() + .zip(caller) + .map(|(changed_by, caller)| changed_by.push(UnsafeCell::new(caller))); } /// Fetches the data pointer to the first element of the [`Column`]. @@ -644,8 +651,7 @@ impl Column { self.data.clear(); self.added_ticks.clear(); self.changed_ticks.clear(); - #[cfg(feature = "track_location")] - self.changed_by.clear(); + self.changed_by.as_mut().map(Vec::clear); } #[inline] @@ -666,9 +672,13 @@ impl Column { /// Users of this API must ensure that accesses to each individual element /// adhere to the safety invariants of [`UnsafeCell`]. #[inline] - #[cfg(feature = "track_location")] - pub fn get_changed_by(&self, row: TableRow) -> Option<&UnsafeCell<&'static Location<'static>>> { - self.changed_by.get(row.as_usize()) + pub fn get_changed_by( + &self, + row: TableRow, + ) -> MaybeLocation>>> { + self.changed_by + .as_ref() + .map(|changed_by| changed_by.get(row.as_usize())) } /// Fetches the calling location that last changed the value at `row`. @@ -678,12 +688,13 @@ impl Column { /// # Safety /// `row` must be within the range `[0, self.len())`. #[inline] - #[cfg(feature = "track_location")] pub unsafe fn get_changed_by_unchecked( &self, row: TableRow, - ) -> &UnsafeCell<&'static Location<'static>> { - debug_assert!(row.as_usize() < self.changed_by.len()); - self.changed_by.get_unchecked(row.as_usize()) + ) -> 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()) + }) } } diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index cd823851cc06b..159a716c4ed05 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -9,13 +9,12 @@ use alloc::{boxed::Box, vec, vec::Vec}; use bevy_platform_support::collections::HashMap; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; pub use column::*; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ alloc::Layout, cell::UnsafeCell, num::NonZeroUsize, ops::{Index, IndexMut}, + panic::Location, }; mod column; @@ -390,14 +389,15 @@ impl Table { } /// Fetches the calling locations that last changed the each component - #[cfg(feature = "track_location")] pub fn get_changed_by_slice_for( &self, component_id: ComponentId, - ) -> Option<&[UnsafeCell<&'static Location<'static>>]> { - 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()) }) + ) -> MaybeLocation>]>> { + 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()) }) + }) } /// Get the specific [`change tick`](Tick) of the component matching `component_id` in `row`. @@ -433,20 +433,22 @@ impl Table { } /// Get the specific calling location that changed the component matching `component_id` in `row` - #[cfg(feature = "track_location")] pub fn get_changed_by( &self, component_id: ComponentId, row: TableRow, - ) -> Option<&UnsafeCell<&'static Location<'static>>> { - (row.as_usize() < self.entity_count()).then_some( - // SAFETY: `row.as_usize()` < `len` - unsafe { - self.get_column(component_id)? - .changed_by - .get_unchecked(row.as_usize()) - }, - ) + ) -> MaybeLocation>>> { + MaybeLocation::new_with_flattened(|| { + (row.as_usize() < 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())) + }, + ) + }) } /// Get the [`ComponentTicks`] of the component matching `component_id` in `row`. @@ -571,9 +573,12 @@ impl Table { .initialize_unchecked(len, UnsafeCell::new(Tick::new(0))); col.changed_ticks .initialize_unchecked(len, UnsafeCell::new(Tick::new(0))); - #[cfg(feature = "track_location")] col.changed_by - .initialize_unchecked(len, UnsafeCell::new(Location::caller())); + .as_mut() + .zip(MaybeLocation::caller()) + .map(|(changed_by, caller)| { + changed_by.initialize_unchecked(len, UnsafeCell::new(caller)); + }); } TableRow::from_usize(len) } @@ -815,18 +820,15 @@ impl Drop for Table { #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{ + change_detection::MaybeLocation, component::{Component, Components, Tick}, entity::Entity, ptr::OwningPtr, - storage::{Storages, TableBuilder, TableId, TableRow, Tables}, + storage::{TableBuilder, TableId, TableRow, Tables}, }; use alloc::vec::Vec; - #[cfg(feature = "track_location")] - use core::panic::Location; - #[derive(Component)] struct W(T); @@ -845,8 +847,7 @@ mod tests { #[test] fn table() { let mut components = Components::default(); - let mut storages = Storages::default(); - let component_id = components.register_component::>(&mut storages); + let component_id = components.register_component::>(); let columns = &[component_id]; let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) @@ -862,8 +863,7 @@ mod tests { row, value_ptr, Tick::new(0), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); }); }; diff --git a/crates/bevy_ecs/src/storage/thin_array_ptr.rs b/crates/bevy_ecs/src/storage/thin_array_ptr.rs index 5654b6da67944..90163440297c8 100644 --- a/crates/bevy_ecs/src/storage/thin_array_ptr.rs +++ b/crates/bevy_ecs/src/storage/thin_array_ptr.rs @@ -87,7 +87,7 @@ impl ThinArrayPtr { /// - The caller should update their saved `capacity` value to reflect the fact that it was changed pub unsafe fn realloc(&mut self, current_capacity: NonZeroUsize, new_capacity: NonZeroUsize) { #[cfg(debug_assertions)] - assert_eq!(self.capacity, current_capacity.into()); + assert_eq!(self.capacity, current_capacity.get()); self.set_capacity(new_capacity.get()); if size_of::() != 0 { let new_layout = diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index c7605764e98f7..6261b9e35587e 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -712,13 +712,14 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)> #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{ entity::Entities, prelude::{Component, Query}, + reflect::ReflectResource, system::{Local, RunSystemOnce}, }; use alloc::vec; + use bevy_reflect::{FromType, Reflect, ReflectRef}; use super::*; @@ -731,8 +732,11 @@ mod tests { #[derive(Component)] struct C; - #[derive(Resource, Default)] - struct R; + #[derive(Resource, Default, Reflect)] + #[reflect(Resource)] + struct R { + foo: usize, + } fn local_system(local: Local) -> u64 { *local @@ -1072,4 +1076,31 @@ mod tests { .build_state(&mut world) .build_system(|_r: ResMut, _fr: FilteredResourcesMut| {}); } + + #[test] + fn filtered_resource_reflect() { + let mut world = World::new(); + world.insert_resource(R { foo: 7 }); + + let system = (FilteredResourcesParamBuilder::new(|builder| { + builder.add_read::(); + }),) + .build_state(&mut world) + .build_system(|res: FilteredResources| { + let reflect_resource = >::from_type(); + let ReflectRef::Struct(reflect_struct) = + reflect_resource.reflect(res).unwrap().reflect_ref() + else { + panic!() + }; + *reflect_struct + .field("foo") + .unwrap() + .try_downcast_ref::() + .unwrap() + }); + + let output = world.run_system_once(system).unwrap(); + assert_eq!(output, 7); + } } diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 13a45e6622fd5..5344534f39568 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -4,11 +4,9 @@ //! It also contains functions that return closures for use with //! [`Commands`](crate::system::Commands). -#[cfg(feature = "track_location")] -use core::panic::Location; - use crate::{ - bundle::{Bundle, InsertMode}, + bundle::{Bundle, InsertMode, NoBundleEffect}, + change_detection::MaybeLocation, entity::Entity, event::{Event, Events}, observer::TriggerTargets, @@ -81,7 +79,11 @@ pub trait HandleError { } } -impl>, T, E: Into> HandleError> for C { +impl HandleError> for C +where + C: Command>, + E: Into, +{ fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command { move |world: &mut World| match self.apply(world) { Ok(_) => {} @@ -90,7 +92,10 @@ impl>, T, E: Into> HandleError> for } } -impl HandleError for C { +impl HandleError for C +where + C: Command, +{ #[inline] fn handle_error_with(self, _error_handler: fn(&mut World, Error)) -> impl Command { self @@ -111,17 +116,11 @@ impl HandleError for C { pub fn spawn_batch(bundles_iter: I) -> impl Command where I: IntoIterator + Send + Sync + 'static, - I::Item: Bundle, + I::Item: Bundle, { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| { - SpawnBatchIter::new( - world, - bundles_iter.into_iter(), - #[cfg(feature = "track_location")] - caller, - ); + SpawnBatchIter::new(world, bundles_iter.into_iter(), caller); } } @@ -135,17 +134,11 @@ where pub fn insert_batch(batch: I, insert_mode: InsertMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| -> Result { - world.try_insert_batch_with_caller( - batch, - insert_mode, - #[cfg(feature = "track_location")] - caller, - )?; + world.try_insert_batch_with_caller(batch, insert_mode, caller)?; Ok(()) } } @@ -162,14 +155,9 @@ pub fn init_resource() -> impl Command { /// A [`Command`] that inserts a [`Resource`] into the world. #[track_caller] pub fn insert_resource(resource: R) -> impl Command { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| { - world.insert_resource_with_caller( - resource, - #[cfg(feature = "track_location")] - caller, - ); + world.insert_resource_with_caller(resource, caller); } } @@ -267,14 +255,9 @@ pub fn run_schedule(label: impl ScheduleLabel) -> impl Command { /// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets. #[track_caller] pub fn trigger(event: impl Event) -> impl Command { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| { - world.trigger_with_caller( - event, - #[cfg(feature = "track_location")] - caller, - ); + world.trigger_with_caller(event, caller); } } @@ -283,29 +266,18 @@ pub fn trigger_targets( event: impl Event, targets: impl TriggerTargets + Send + Sync + 'static, ) -> impl Command { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| { - world.trigger_targets_with_caller( - event, - targets, - #[cfg(feature = "track_location")] - caller, - ); + world.trigger_targets_with_caller(event, targets, caller); } } /// A [`Command`] that sends an arbitrary [`Event`]. #[track_caller] pub fn send_event(event: E) -> impl Command { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |world: &mut World| { let mut events = world.resource_mut::>(); - events.send_with_caller( - event, - #[cfg(feature = "track_location")] - caller, - ); + events.send_with_caller(event, caller); } } diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index e5a1097abcf82..f24dbf8bba72b 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -5,19 +5,18 @@ //! [`EntityCommands`](crate::system::EntityCommands). use alloc::vec::Vec; +use core::fmt; use log::info; -#[cfg(feature = "track_location")] -use core::panic::Location; - use crate::{ bundle::{Bundle, InsertMode}, + change_detection::MaybeLocation, component::{Component, ComponentId, ComponentInfo}, - entity::{Entity, EntityCloneBuilder}, + entity::{Entity, EntityClonerBuilder}, event::Event, result::Result, system::{command::HandleError, Command, IntoObserverSystem}, - world::{error::EntityFetchError, EntityWorldMut, FromWorld, World}, + world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World}, }; use bevy_ptr::OwningPtr; @@ -81,8 +80,7 @@ use bevy_ptr::OwningPtr; /// } /// ``` pub trait EntityCommand: Send + 'static { - /// Executes this command for the given [`Entity`] and - /// returns a [`Result`] for error handling. + /// 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 @@ -98,13 +96,16 @@ pub trait CommandWithEntity { fn with_entity(self, entity: Entity) -> impl Command + HandleError; } -impl CommandWithEntity> for C { +impl CommandWithEntity> for C +where + C: EntityCommand, +{ fn with_entity( self, entity: Entity, - ) -> impl Command> + HandleError> - { - move |world: &mut World| -> Result<(), EntityFetchError> { + ) -> impl Command> + + HandleError> { + move |world: &mut World| -> Result<(), EntityMutableFetchError> { let entity = world.get_entity_mut(entity)?; self.apply(entity); Ok(()) @@ -112,11 +113,10 @@ impl CommandWithEntity> for C { } } -impl< - C: EntityCommand>, - T, - Err: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static, - > CommandWithEntity>> for C +impl CommandWithEntity>> for C +where + C: EntityCommand>, + Err: fmt::Debug + fmt::Display + Send + Sync + 'static, { fn with_entity( self, @@ -136,7 +136,7 @@ impl< pub enum EntityCommandError { /// The entity this [`EntityCommand`] tried to run on could not be fetched. #[error(transparent)] - EntityFetchError(#[from] EntityFetchError), + EntityFetchError(#[from] EntityMutableFetchError), /// An error that occurred while running the [`EntityCommand`]. #[error("{0}")] CommandFailed(E), @@ -155,15 +155,9 @@ where /// replacing any that were already present. #[track_caller] pub fn insert(bundle: impl Bundle) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.insert_with_caller( - bundle, - InsertMode::Replace, - #[cfg(feature = "track_location")] - caller, - ); + entity.insert_with_caller(bundle, InsertMode::Replace, caller); } } @@ -171,34 +165,22 @@ pub fn insert(bundle: impl Bundle) -> impl EntityCommand { /// except for any that were already present. #[track_caller] pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.insert_with_caller( - bundle, - InsertMode::Keep, - #[cfg(feature = "track_location")] - caller, - ); + entity.insert_with_caller(bundle, InsertMode::Keep, caller); } } /// An [`EntityCommand`] that adds a dynamic component to an entity. #[track_caller] pub fn insert_by_id(component_id: ComponentId, value: T) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { // SAFETY: // - `component_id` safety is ensured by the caller // - `ptr` is valid within the `make` block OwningPtr::make(value, |ptr| unsafe { - entity.insert_by_id_with_caller( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + entity.insert_by_id_with_caller(component_id, ptr, caller); }); } } @@ -207,29 +189,19 @@ pub fn insert_by_id(component_id: ComponentId, value: T) -> i /// the component's [`FromWorld`] implementation. #[track_caller] pub fn insert_from_world(mode: InsertMode) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { let value = entity.world_scope(|world| T::from_world(world)); - entity.insert_with_caller( - value, - mode, - #[cfg(feature = "track_location")] - caller, - ); + entity.insert_with_caller(value, mode, caller); } } /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity. #[track_caller] pub fn remove() -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.remove_with_caller::( - #[cfg(feature = "track_location")] - caller, - ); + entity.remove_with_caller::(caller); } } @@ -237,40 +209,27 @@ pub fn remove() -> impl EntityCommand { /// as well as the required components for each component removed. #[track_caller] pub fn remove_with_requires() -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.remove_with_requires_with_caller::( - #[cfg(feature = "track_location")] - caller, - ); + entity.remove_with_requires_with_caller::(caller); } } /// An [`EntityCommand`] that removes a dynamic component from an entity. #[track_caller] pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.remove_by_id_with_caller( - component_id, - #[cfg(feature = "track_location")] - caller, - ); + entity.remove_by_id_with_caller(component_id, caller); } } /// An [`EntityCommand`] that removes all components from an entity. #[track_caller] pub fn clear() -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.clear_with_caller( - #[cfg(feature = "track_location")] - caller, - ); + entity.clear_with_caller(caller); } } @@ -278,13 +237,9 @@ pub fn clear() -> impl EntityCommand { /// except for those in the given [`Bundle`]. #[track_caller] pub fn retain() -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.retain_with_caller::( - #[cfg(feature = "track_location")] - caller, - ); + entity.retain_with_caller::(caller); } } @@ -292,17 +247,14 @@ 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 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. #[track_caller] pub fn despawn() -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |entity: EntityWorldMut| { - entity.despawn_with_caller( - #[cfg(feature = "track_location")] - caller, - ); + entity.despawn_with_caller(caller); } } @@ -312,22 +264,17 @@ pub fn despawn() -> impl EntityCommand { pub fn observe( observer: impl IntoObserverSystem, ) -> impl EntityCommand { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { - entity.observe_with_caller( - observer, - #[cfg(feature = "track_location")] - caller, - ); + entity.observe_with_caller(observer, caller); } } /// An [`EntityCommand`] that clones parts of an entity onto another entity, -/// configured through [`EntityCloneBuilder`]. +/// configured through [`EntityClonerBuilder`]. pub fn clone_with( target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> impl EntityCommand { move |mut entity: EntityWorldMut| { entity.clone_with(target, config); @@ -356,6 +303,7 @@ pub fn log_components() -> impl EntityCommand { let debug_infos: Vec<_> = entity .world() .inspect_entity(entity.id()) + .expect("Entity existence is verified before an EntityCommand is executed") .map(ComponentInfo::name) .collect(); info!("Entity {}: {debug_infos:?}", entity.id()); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 5694df4a49451..0208a4f672535 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -13,15 +13,14 @@ pub use parallel_scope::*; use alloc::boxed::Box; use core::marker::PhantomData; -use core::panic::Location; use log::error; use crate::{ self as bevy_ecs, - bundle::{Bundle, InsertMode}, - change_detection::Mut, + bundle::{Bundle, InsertMode, NoBundleEffect}, + change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, - entity::{Entities, Entity, EntityCloneBuilder}, + entity::{Entities, Entity, EntityClonerBuilder}, event::Event, observer::{Observer, TriggerTargets}, resource::Resource, @@ -90,14 +89,19 @@ use crate::{ /// /// # Error handling /// -/// Commands can return a [`Result`](crate::result::Result), which can be passed to -/// an error handler. Error handlers are functions/closures of the form -/// `fn(&mut World, CommandError)`. +/// A [`Command`] can return a [`Result`](crate::result::Result), +/// which will be passed to an error handler if the `Result` is an error. /// -/// The default error handler panics. It can be configured by enabling the `configurable_error_handler` -/// cargo feature, then setting the `GLOBAL_ERROR_HANDLER`. +/// 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. /// -/// Alternatively, you can customize the error handler for a specific command by calling [`Commands::queue_handled`]. +/// 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`. +/// +/// 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. /// @@ -540,14 +544,15 @@ impl<'w, 's> Commands<'w, 's> { pub fn spawn_batch(&mut self, bundles_iter: I) where I: IntoIterator + Send + Sync + 'static, - I::Item: Bundle, + I::Item: Bundle, { self.queue(command::spawn_batch(bundles_iter)); } /// 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). + /// If the [`Command`] returns a [`Result`], + /// it will be handled using the [default error handler](error_handler::default). /// /// To use a custom error handler, see [`Commands::queue_handled`]. /// @@ -590,8 +595,11 @@ impl<'w, 's> Commands<'w, 's> { pub fn queue + HandleError, T>(&mut self, command: C) { self.queue_internal(command.handle_error()); } - /// Pushes a generic [`Command`] to the command queue. If the command returns a [`Result`] the given - /// `error_handler` will be used to handle error cases. + + /// Pushes a generic [`Command`] to the command queue. + /// + /// If the [`Command`] returns a [`Result`], + /// the given `error_handler` will be used to handle error cases. /// /// To implicitly use the default error handler, see [`Commands::queue`]. /// @@ -681,13 +689,12 @@ impl<'w, 's> Commands<'w, 's> { pub fn insert_or_spawn_batch(&mut self, bundles_iter: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { - let caller = Location::caller(); + let caller = MaybeLocation::caller(); self.queue(move |world: &mut World| { if let Err(invalid_entities) = world.insert_or_spawn_batch_with_caller( bundles_iter, - #[cfg(feature = "track_location")] caller, ) { error!( @@ -721,7 +728,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { self.queue(command::insert_batch(batch, InsertMode::Replace)); } @@ -748,7 +755,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { self.queue(command::insert_batch(batch, InsertMode::Keep)); } @@ -773,7 +780,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn try_insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { self.queue( command::insert_batch(batch, InsertMode::Replace) @@ -801,7 +808,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn try_insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle, { self.queue( command::insert_batch(batch, InsertMode::Keep).handle_error_with(error_handler::warn()), @@ -1139,7 +1146,7 @@ impl<'w, 's> Commands<'w, 's> { /// 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`](bevy_ecs::schedule::Schedule), one by one, +/// The command queue is applied between [`Schedules`](crate::schedule::Schedule), one by one, /// so that each command can have exclusive access to the World. /// /// # Fallible @@ -1150,14 +1157,19 @@ impl<'w, 's> Commands<'w, 's> { /// /// # Error handling /// -/// [`EntityCommands`] can return a [`Result`](crate::result::Result), which can be passed to -/// an error handler. Error handlers are functions/closures of the form -/// `fn(&mut World, CommandError)`. +/// An [`EntityCommand`] can return a [`Result`](crate::result::Result), +/// which will be passed to an error handler 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 panics. It can be configured by enabling the `configurable_error_handler` -/// cargo feature, then setting the `GLOBAL_ERROR_HANDLER`. +/// 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`. /// -/// Alternatively, you can customize the error handler for a specific command by calling [`EntityCommands::queue_handled`]. +/// 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. pub struct EntityCommands<'a> { @@ -1756,7 +1768,8 @@ impl<'a> EntityCommands<'a> { /// 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). + /// If the [`EntityCommand`] returns a [`Result`], + /// it will be handled using the [default error handler](error_handler::default). /// /// To use a custom error handler, see [`EntityCommands::queue_handled`]. /// @@ -1790,7 +1803,9 @@ impl<'a> EntityCommands<'a> { } /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. - /// If the command returns a [`Result`] the given `error_handler` will be used to handle error cases. + /// + /// If the [`EntityCommand`] returns a [`Result`], + /// the given `error_handler` will be used to handle error cases. /// /// To implicitly use the default error handler, see [`EntityCommands::queue`]. /// @@ -1913,7 +1928,7 @@ impl<'a> EntityCommands<'a> { } /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityCloneBuilder`]. + /// configured through [`EntityClonerBuilder`]. /// /// By default, the other entity will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). @@ -1924,7 +1939,7 @@ impl<'a> EntityCommands<'a> { /// /// # Example /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// @@ -1948,14 +1963,11 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(example_system); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. pub fn clone_with( &mut self, target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.queue(entity_command::clone_with(target, config)) } @@ -1996,16 +2008,16 @@ impl<'a> EntityCommands<'a> { } /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityCloneBuilder`], returning the [`EntityCommands`] of the clone. + /// using [`EntityClonerBuilder`], returning the [`EntityCommands`] of the clone. /// /// By default, the clone will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// - /// To exclude specific components, use [`EntityCloneBuilder::deny`]. - /// To only include specific components, use [`EntityCloneBuilder::deny_all`] - /// followed by [`EntityCloneBuilder::allow`]. + /// To exclude specific components, use [`EntityClonerBuilder::deny`]. + /// To only include specific components, use [`EntityClonerBuilder::deny_all`] + /// followed by [`EntityClonerBuilder::allow`]. /// - /// See the methods on [`EntityCloneBuilder`] for more options. + /// See the methods on [`EntityClonerBuilder`] for more options. /// /// # Note /// @@ -2034,7 +2046,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(example_system); pub fn clone_and_spawn_with( &mut self, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> EntityCommands<'_> { let entity_clone = self.commands().spawn_empty().id(); self.clone_with(entity_clone, config); @@ -2206,7 +2218,6 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, component::{require, Component}, resource::Resource, system::Commands, diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 82a00ceac2977..2f471c13c5449 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -1,7 +1,6 @@ use bevy_utils::Parallel; use crate::{ - self as bevy_ecs, entity::Entities, prelude::World, system::{Deferred, SystemBuffer, SystemMeta, SystemParam}, diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index e36b7ea759bdd..f271e32e2f714 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -136,7 +136,6 @@ all_tuples!( #[cfg(test)] mod tests { - use crate as bevy_ecs; use crate::{schedule::Schedule, system::Local, world::World}; use alloc::vec::Vec; use bevy_ecs_macros::Resource; diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index d5cbd5ffb1571..4fe489d253d1e 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -330,11 +330,10 @@ mod tests { use std::println; use crate::{ - self as bevy_ecs, archetype::{ArchetypeComponentId, Archetypes}, bundle::Bundles, change_detection::DetectChanges, - component::{Component, Components, Tick}, + component::{Component, Components}, entity::{Entities, Entity}, prelude::{AnyOf, EntityRef}, query::{Added, Changed, Or, With, Without}, @@ -1361,7 +1360,7 @@ mod tests { fn mutable_query(mut query: Query<&mut A>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<&A>) {} @@ -1376,7 +1375,7 @@ mod tests { fn mutable_query(mut query: Query>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query>) {} @@ -1391,7 +1390,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &B)>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B)>) {} @@ -1406,7 +1405,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &mut B)>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B)>) {} @@ -1421,7 +1420,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &mut B), With>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B), With>) {} @@ -1436,7 +1435,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &mut B), Without>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B), Without>) {} @@ -1451,7 +1450,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &mut B), Added>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B), Added>) {} @@ -1466,7 +1465,7 @@ mod tests { fn mutable_query(mut query: Query<(&mut A, &mut B), Changed>) { for _ in &mut query {} - immutable_query(query.to_readonly()); + immutable_query(query.as_readonly()); } fn immutable_query(_: Query<(&A, &B), Changed>) {} @@ -1572,24 +1571,6 @@ mod tests { }); } - #[test] - #[should_panic = "Encountered a mismatched World."] - fn query_validates_world_id() { - let mut world1 = World::new(); - let world2 = World::new(); - let qstate = world1.query::<()>(); - // SAFETY: doesnt access anything - let query = unsafe { - Query::new( - world2.as_unsafe_world_cell_readonly(), - &qstate, - Tick::new(0), - Tick::new(0), - ) - }; - query.iter(); - } - #[test] #[should_panic] fn assert_system_does_not_conflict() { diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index fa4f6035b8884..8a8f82d99e361 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -1,22 +1,27 @@ +use alloc::{borrow::Cow, vec::Vec}; +use core::marker::PhantomData; + use crate::{ + archetype::ArchetypeComponentId, + component::{ComponentId, Tick}, prelude::{Bundle, Trigger}, - system::System, + query::Access, + result::Result, + schedule::{Fallible, Infallible}, + system::{input::SystemIn, System}, + world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World}, }; use super::IntoSystem; /// Implemented for [`System`]s that have a [`Trigger`] as the first argument. -pub trait ObserverSystem: +pub trait ObserverSystem: System, Out = Out> + Send + 'static { } -impl< - E: 'static, - B: Bundle, - Out, - T: System, Out = Out> + Send + 'static, - > ObserverSystem for T +impl ObserverSystem for T where + T: System, Out = Out> + Send + 'static { } @@ -32,7 +37,7 @@ impl< label = "the trait `IntoObserverSystem` is not implemented", note = "for function `ObserverSystem`s, ensure the first argument is a `Trigger` and any subsequent ones are `SystemParam`" )] -pub trait IntoObserverSystem: Send + 'static { +pub trait IntoObserverSystem: Send + 'static { /// The type of [`System`] that this instance converts into. type System: ObserverSystem; @@ -40,27 +45,153 @@ pub trait IntoObserverSystem: Send + 'static fn into_system(this: Self) -> Self::System; } -impl< - S: IntoSystem, Out, M> + Send + 'static, - M, - Out, - E: 'static, - B: Bundle, - > IntoObserverSystem for S +impl IntoObserverSystem for S where + S: IntoSystem, Out, M> + Send + 'static, S::System: ObserverSystem, + E: 'static, + B: Bundle, { - type System = , Out, M>>::System; + type System = S::System; fn into_system(this: Self) -> Self::System { IntoSystem::into_system(this) } } +impl IntoObserverSystem for S +where + S: IntoSystem, (), M> + Send + 'static, + S::System: ObserverSystem, + E: Send + Sync + 'static, + B: Bundle, +{ + type System = InfallibleObserverWrapper; + + fn into_system(this: Self) -> Self::System { + InfallibleObserverWrapper::new(IntoSystem::into_system(this)) + } +} + +/// A wrapper that converts an observer system that returns `()` into one that returns `Ok(())`. +pub struct InfallibleObserverWrapper { + observer: S, + _marker: PhantomData<(E, B)>, +} + +impl InfallibleObserverWrapper { + /// Create a new `InfallibleObserverWrapper`. + pub fn new(observer: S) -> Self { + Self { + observer, + _marker: PhantomData, + } + } +} + +impl System for InfallibleObserverWrapper +where + S: ObserverSystem, + E: Send + Sync + 'static, + B: Bundle, +{ + type In = Trigger<'static, E, B>; + type Out = Result; + + #[inline] + fn name(&self) -> Cow<'static, str> { + self.observer.name() + } + + #[inline] + fn component_access(&self) -> &Access { + self.observer.component_access() + } + + #[inline] + fn archetype_component_access(&self) -> &Access { + self.observer.archetype_component_access() + } + + #[inline] + fn is_send(&self) -> bool { + self.observer.is_send() + } + + #[inline] + fn is_exclusive(&self) -> bool { + self.observer.is_exclusive() + } + + #[inline] + fn has_deferred(&self) -> bool { + self.observer.has_deferred() + } + + #[inline] + unsafe fn run_unsafe( + &mut self, + input: SystemIn<'_, Self>, + world: UnsafeWorldCell, + ) -> Self::Out { + self.observer.run_unsafe(input, world); + 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); + } + + #[inline] + fn queue_deferred(&mut self, world: DeferredWorld) { + self.observer.queue_deferred(world); + } + + #[inline] + unsafe fn validate_param_unsafe(&mut self, world: UnsafeWorldCell) -> bool { + self.observer.validate_param_unsafe(world) + } + + #[inline] + fn initialize(&mut self, world: &mut World) { + 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); + } + + #[inline] + fn get_last_run(&self) -> Tick { + self.observer.get_last_run() + } + + #[inline] + fn set_last_run(&mut self, last_run: Tick) { + self.observer.set_last_run(last_run); + } + + fn default_system_sets(&self) -> Vec { + self.observer.default_system_sets() + } +} + #[cfg(test)] mod tests { use crate::{ - self as bevy_ecs, event::Event, observer::Trigger, system::{In, IntoSystem}, diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index dbeaa98c48a23..a0457dc667b71 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,16 +1,17 @@ use crate::{ batching::BatchingStrategy, component::Tick, - entity::{Entity, EntityBorrow, EntitySet}, + entity::{Entity, EntityBorrow, EntityDoesNotExistError, EntitySet}, query::{ - QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, - QueryManyUniqueIter, QueryParIter, QuerySingleError, QueryState, ROQueryItem, - ReadOnlyQueryData, + DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, + QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, + QueryParManyUniqueIter, QuerySingleError, QueryState, ROQueryItem, ReadOnlyQueryData, }, world::unsafe_world_cell::UnsafeWorldCell, }; use core::{ marker::PhantomData, + mem::MaybeUninit, ops::{Deref, DerefMut}, }; @@ -381,6 +382,14 @@ pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { this_run: Tick, } +impl Clone for Query<'_, '_, D, F> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Query<'_, '_, D, F> {} + impl core::fmt::Debug for Query<'_, '_, D, F> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Query") @@ -396,14 +405,11 @@ impl core::fmt::Debug for Query<'_, '_, D, F> { impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// Creates a new query. /// - /// # Panics - /// - /// This will panic if the world used to create `state` is not `world`. - /// /// # Safety /// - /// This will create a query that could violate memory safety rules. Make sure that this is only - /// called in ways that ensure the queries have unique mutable access. + /// * This will create a query that could violate memory safety rules. Make sure that this is only + /// called in ways that ensure the queries have unique mutable access. + /// * `world` must be the world used to create `state`. #[inline] pub(crate) unsafe fn new( world: UnsafeWorldCell<'w>, @@ -411,8 +417,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { last_run: Tick, this_run: Tick, ) -> Self { - state.validate_world(world.id()); - Self { world, state, @@ -426,9 +430,39 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// For example, `Query<(&mut D1, &D2, &mut D3), With>` will become `Query<(&D1, &D2, &D3), With>`. /// This can be useful when working around the borrow checker, /// or reusing functionality between systems via functions that accept query types. - pub fn to_readonly(&self) -> Query<'_, 's, D::ReadOnly, F> { + /// + /// # See also + /// + /// [`into_readonly`](Self::into_readonly) for a version that consumes the `Query` to return one with the full `'world` lifetime. + pub fn as_readonly(&self) -> Query<'_, 's, D::ReadOnly, F> { + // SAFETY: The reborrowed query is converted to read-only, so it cannot perform mutable access, + // and the original query is held with a shared borrow, so it cannot perform mutable access either. + unsafe { self.reborrow_unsafe() }.into_readonly() + } + + /// Returns another `Query` from this does not return any data, which can be faster. + fn as_nop(&self) -> Query<'w, 's, NopWorldQuery, F> { + let new_state = self.state.as_nop(); + // SAFETY: + // - This is memory safe because it performs no access. + // - The world matches because it was the same one used to construct self. + unsafe { Query::new(self.world, new_state, self.last_run, self.this_run) } + } + + /// Returns another `Query` from this that fetches the read-only version of the query items. + /// + /// For example, `Query<(&mut D1, &D2, &mut D3), With>` will become `Query<(&D1, &D2, &D3), With>`. + /// This can be useful when working around the borrow checker, + /// or reusing functionality between systems via functions that accept query types. + /// + /// # See also + /// + /// [`as_readonly`](Self::as_readonly) for a version that borrows the `Query` instead of consuming it. + pub fn into_readonly(self) -> Query<'w, 's, D::ReadOnly, F> { let new_state = self.state.as_readonly(); - // SAFETY: This is memory safe because it turns the query immutable. + // SAFETY: + // - This is memory safe because it turns the query immutable. + // - The world matches because it was the same one used to construct self. unsafe { Query::new(self.world, new_state, self.last_run, self.this_run) } } @@ -459,6 +493,42 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn reborrow(&mut self) -> Query<'_, 's, D, F> { // SAFETY: this query is exclusively borrowed while the new one exists, so // no overlapping access can occur. + unsafe { self.reborrow_unsafe() } + } + + /// Returns a new `Query` reborrowing the access from this one. + /// The current query will still be usable while the new one exists, but must not be used in a way that violates aliasing. + /// + /// # Safety + /// + /// This function makes it possible to violate Rust's aliasing guarantees. + /// You must make sure this call does not result in a mutable or shared reference to a component with a mutable reference. + /// + /// # See also + /// + /// - [`reborrow`](Self::reborrow) for the safe versions. + pub unsafe fn reborrow_unsafe(&self) -> Query<'_, 's, D, F> { + // SAFETY: + // - This is memory safe because the caller ensures that there are no conflicting references. + // - The world matches because it was the same one used to construct self. + unsafe { self.copy_unsafe() } + } + + /// Returns a new `Query` copying the access from this one. + /// The current query will still be usable while the new one exists, but must not be used in a way that violates aliasing. + /// + /// # Safety + /// + /// This function makes it possible to violate Rust's aliasing guarantees. + /// You must make sure this call does not result in a mutable or shared reference to a component with a mutable reference. + /// + /// # See also + /// + /// - [`reborrow_unsafe`](Self::reborrow_unsafe) for a safer version that constrains the returned `'w` lifetime to the length of the borrow. + unsafe fn copy_unsafe(&self) -> Query<'w, 's, D, F> { + // SAFETY: + // - This is memory safe because the caller ensures that there are no conflicting references. + // - The world matches because it was the same one used to construct self. unsafe { Query::new(self.world, self.state, self.last_run, self.this_run) } } @@ -490,14 +560,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`iter_mut`](Self::iter_mut) for mutable query items. #[inline] pub fn iter(&self) -> QueryIter<'_, 's, D::ReadOnly, F> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The query is read-only, so it can be aliased even if it was originally mutable. - unsafe { - self.state - .as_readonly() - .iter_unchecked_manual(self.world, self.last_run, self.this_run) - } + self.as_readonly().into_iter() } /// Returns an [`Iterator`] over the query items. @@ -528,11 +591,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`iter`](Self::iter) for read-only query items. #[inline] pub fn iter_mut(&mut self) -> QueryIter<'_, 's, D, F> { - // SAFETY: `self.world` has permission to access the required components. - unsafe { - self.state - .iter_unchecked_manual(self.world, self.last_run, self.this_run) - } + self.reborrow().into_iter() } /// Returns a [`QueryCombinationIter`] over all combinations of `K` read-only query items without repetition. @@ -557,20 +616,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. + /// - [`iter_combinations_inner`](Self::iter_combinations_inner) for mutable query item combinations with the full `'world` lifetime. #[inline] pub fn iter_combinations( &self, ) -> QueryCombinationIter<'_, 's, D::ReadOnly, F, K> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The query is read-only, so it can be aliased even if it was originally mutable. - unsafe { - self.state.as_readonly().iter_combinations_unchecked_manual( - self.world, - self.last_run, - self.this_run, - ) - } + self.as_readonly().iter_combinations_inner() } /// Returns a [`QueryCombinationIter`] over all combinations of `K` query items without repetition. @@ -595,15 +646,42 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. + /// - [`iter_combinations_inner`](Self::iter_combinations_inner) for mutable query item combinations with the full `'world` lifetime. #[inline] pub fn iter_combinations_mut( &mut self, ) -> QueryCombinationIter<'_, 's, D, F, K> { + self.reborrow().iter_combinations_inner() + } + + /// Returns a [`QueryCombinationIter`] over all combinations of `K` query items without repetition. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// This iterator is always guaranteed to return results from each unique pair of matching entities. + /// Iteration order is not guaranteed. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Component)] + /// # struct ComponentA; + /// fn some_system(query: Query<&mut ComponentA>) { + /// let mut combinations = query.iter_combinations_inner(); + /// while let Some([mut a1, mut a2]) = combinations.fetch_next() { + /// // mutably access components data + /// } + /// } + /// ``` + /// + /// # See also + /// + /// - [`iter_combinations`](Self::iter_combinations) for read-only query item combinations. + /// - [`iter_combinations_mut`](Self::iter_combinations_mut) for mutable query item combinations. + #[inline] + pub fn iter_combinations_inner(self) -> QueryCombinationIter<'w, 's, D, F, K> { // SAFETY: `self.world` has permission to access the required components. - unsafe { - self.state - .iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run) - } + unsafe { QueryCombinationIter::new(self.world, self.state, self.last_run, self.this_run) } } /// Returns an [`Iterator`] over the read-only query items generated from an [`Entity`] list. @@ -642,22 +720,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`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>( &self, entities: EntityList, ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The query is read-only, so it can be aliased even if it was originally mutable. - unsafe { - self.state.as_readonly().iter_many_unchecked_manual( - entities, - self.world, - self.last_run, - self.this_run, - ) - } + self.as_readonly().iter_many_inner(entities) } /// Returns an iterator over the query items generated from an [`Entity`] list. @@ -693,16 +762,39 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` + /// # See also + /// + /// - [`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>( &mut self, entities: EntityList, ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { + self.reborrow().iter_many_inner(entities) + } + + /// Returns an iterator over the query items generated from an [`Entity`] list. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// Items are returned in the order of the list of entities, and may not be unique if the input + /// doesn't guarantee uniqueness. Entities that don't match the query are skipped. + /// + /// # See also + /// + /// - [`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>( + self, + entities: EntityList, + ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { - self.state.iter_many_unchecked_manual( - entities, + QueryManyIter::new( self.world, + self.state, + entities, self.last_run, self.this_run, ) @@ -756,22 +848,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. + /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. #[inline] pub fn iter_many_unique( &self, entities: EntityList, ) -> QueryManyUniqueIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The query is read-only, so it can be aliased even if it was originally mutable. - unsafe { - self.state.as_readonly().iter_many_unique_unchecked_manual( - entities, - self.world, - self.last_run, - self.this_run, - ) - } + self.as_readonly().iter_many_unique_inner(entities) } /// Returns an iterator over the unique query items generated from an [`EntitySet`]. @@ -817,16 +900,76 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` + /// # See also + /// + /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. + /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. #[inline] pub fn iter_many_unique_mut( &mut self, entities: EntityList, ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { + self.reborrow().iter_many_unique_inner(entities) + } + + /// Returns an iterator over the unique query items generated from an [`EntitySet`]. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// Items are returned in the order of the list of entities. Entities that don't match the query are skipped. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, entity::{EntitySet, UniqueEntityIter}}; + /// # use core::slice; + /// #[derive(Component)] + /// struct Counter { + /// value: i32 + /// } + /// + /// // `Friends` ensures that it only lists unique entities. + /// #[derive(Component)] + /// struct Friends { + /// unique_list: Vec, + /// } + /// + /// impl<'a> IntoIterator for &'a Friends { + /// 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()) } + /// } + /// } + /// + /// fn system( + /// friends_query: Query<&Friends>, + /// mut counter_query: Query<&mut Counter>, + /// ) { + /// let friends = friends_query.single(); + /// for mut counter in counter_query.iter_many_unique_inner(friends) { + /// println!("Friend's counter: {:?}", counter.value); + /// counter.value += 1; + /// } + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + /// # See also + /// + /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. + /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. + #[inline] + pub fn iter_many_unique_inner( + self, + entities: EntityList, + ) -> QueryManyUniqueIter<'w, 's, D, F, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { - self.state.iter_many_unique_unchecked_manual( - entities, + QueryManyUniqueIter::new( self.world, + self.state, + entities, self.last_run, self.this_run, ) @@ -848,13 +991,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`iter`](Self::iter) and [`iter_mut`](Self::iter_mut) for the safe versions. #[inline] pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, 's, D, F> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The caller ensures that this operation will not result in any aliased mutable accesses. - unsafe { - self.state - .iter_unchecked_manual(self.world, self.last_run, self.this_run) - } + // SAFETY: The caller promises that this will not result in multiple mutable references. + unsafe { self.reborrow_unsafe() }.into_iter() } /// Iterates over all possible combinations of `K` query items without repetition. @@ -874,13 +1012,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub unsafe fn iter_combinations_unsafe( &self, ) -> QueryCombinationIter<'_, 's, D, F, K> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The caller ensures that this operation will not result in any aliased mutable accesses. - unsafe { - self.state - .iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run) - } + // SAFETY: The caller promises that this will not result in multiple mutable references. + unsafe { self.reborrow_unsafe() }.iter_combinations_inner() } /// Returns an [`Iterator`] over the query items generated from an [`Entity`] list. @@ -901,17 +1034,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { &self, entities: EntityList, ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The caller ensures that this operation will not result in any aliased mutable accesses. - unsafe { - self.state.iter_many_unchecked_manual( - entities, - self.world, - self.last_run, - self.this_run, - ) - } + // SAFETY: The caller promises that this will not result in multiple mutable references. + unsafe { self.reborrow_unsafe() }.iter_many_inner(entities) } /// Returns an [`Iterator`] over the unique query items generated from an [`Entity`] list. @@ -925,22 +1049,15 @@ 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. + /// - [`iter_many_unique`](Self::iter_many_unique) to get read-only query items. + /// - [`iter_many_unique_mut`](Self::iter_many_unique_mut) to get mutable query items. + /// - [`iter_many_unique_inner`](Self::iter_many_unique_inner) to get with the actual "inner" world lifetime. pub unsafe fn iter_many_unique_unsafe( &self, entities: EntityList, ) -> QueryManyUniqueIter<'_, 's, D, F, EntityList::IntoIter> { - // SAFETY: - // - `self.world` has permission to access the required components. - // - The caller ensures that this operation will not result in any aliased mutable accesses. - unsafe { - self.state.iter_many_unique_unchecked_manual( - entities, - self.world, - self.last_run, - self.this_run, - ) - } + // SAFETY: The caller promises that this will not result in multiple mutable references. + unsafe { self.reborrow_unsafe() }.iter_many_unique_inner(entities) } /// Returns a parallel iterator over the query results for the given [`World`]. @@ -960,13 +1077,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`World`]: crate::world::World #[inline] pub fn par_iter(&self) -> QueryParIter<'_, '_, D::ReadOnly, F> { - QueryParIter { - world: self.world, - state: self.state.as_readonly(), - last_run: self.last_run, - this_run: self.this_run, - batching_strategy: BatchingStrategy::new(), - } + self.as_readonly().par_iter_inner() } /// Returns a parallel iterator over the query results for the given [`World`]. @@ -1001,6 +1112,37 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`World`]: crate::world::World #[inline] pub fn par_iter_mut(&mut self) -> QueryParIter<'_, '_, D, F> { + self.reborrow().par_iter_inner() + } + + /// Returns a parallel iterator over the query results for the given [`World`](crate::world::World). + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// This parallel iterator is always guaranteed to return results from each matching entity once and + /// only once. Iteration order and thread assignment is not guaranteed. + /// + /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] + /// on [`QueryIter`]. + /// + /// # Example + /// + /// Here, the `gravity_system` updates the `Velocity` component of every entity that contains it: + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct Velocity { x: f32, y: f32, z: f32 } + /// fn gravity_system(query: Query<&mut Velocity>) { + /// const DELTA: f32 = 1.0 / 60.0; + /// query.par_iter_inner().for_each(|mut velocity| { + /// velocity.y -= 9.8 * DELTA; + /// }); + /// } + /// # bevy_ecs::system::assert_is_system(gravity_system); + /// ``` + #[inline] + pub fn par_iter_inner(self) -> QueryParIter<'w, 's, D, F> { QueryParIter { world: self.world, state: self.state, @@ -1010,6 +1152,94 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } } + /// Returns a parallel iterator over the read-only query items generated from an [`Entity`] list. + /// + /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. + /// + /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] + /// on [`QueryManyIter`]. + /// + /// This can only be called for read-only queries. To avoid potential aliasing, there is no `par_iter_many_mut` equivalent. + /// See [`par_iter_many_unique_mut`] for an alternative using [`EntitySet`]. + /// + /// Note that you must use the `for_each` method to iterate over the + /// results, see [`par_iter_mut`] for an example. + /// + /// [`par_iter_many_unique_mut`]: Self::par_iter_many_unique_mut + /// [`par_iter_mut`]: Self::par_iter_mut + #[inline] + pub fn par_iter_many>( + &self, + entities: EntityList, + ) -> QueryParManyIter<'_, '_, D::ReadOnly, F, EntityList::Item> { + QueryParManyIter { + world: self.world, + state: self.state.as_readonly(), + entity_list: entities.into_iter().collect(), + last_run: self.last_run, + this_run: self.this_run, + batching_strategy: BatchingStrategy::new(), + } + } + + /// Returns a parallel iterator over the unique read-only query items generated from an [`EntitySet`]. + /// + /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. + /// + /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] + /// on [`QueryManyUniqueIter`]. + /// + /// This can only be called for read-only queries, see [`par_iter_many_unique_mut`] for write-queries. + /// + /// Note that you must use the `for_each` method to iterate over the + /// results, see [`par_iter_mut`] for an example. + /// + /// [`par_iter_many_unique_mut`]: Self::par_iter_many_unique_mut + /// [`par_iter_mut`]: Self::par_iter_mut + #[inline] + pub fn par_iter_many_unique>( + &self, + entities: EntityList, + ) -> QueryParManyUniqueIter<'_, '_, D::ReadOnly, F, EntityList::Item> { + QueryParManyUniqueIter { + world: self.world, + state: self.state.as_readonly(), + entity_list: entities.into_iter().collect(), + last_run: self.last_run, + this_run: self.this_run, + batching_strategy: BatchingStrategy::new(), + } + } + + /// Returns a parallel iterator over the unique query items generated from an [`EntitySet`]. + /// + /// Entities that don't match the query are skipped. Iteration order and thread assignment is not guaranteed. + /// + /// If the `multithreaded` feature is disabled, iterating with this operates identically to [`Iterator::for_each`] + /// on [`QueryManyUniqueIter`]. + /// + /// This can only be called for mutable queries, see [`par_iter_many_unique`] for read-only-queries. + /// + /// Note that you must use the `for_each` method to iterate over the + /// results, see [`par_iter_mut`] for an example. + /// + /// [`par_iter_many_unique`]: Self::par_iter_many_unique + /// [`par_iter_mut`]: Self::par_iter_mut + #[inline] + pub fn par_iter_many_unique_mut>( + &mut self, + entities: EntityList, + ) -> QueryParManyUniqueIter<'_, '_, D, F, EntityList::Item> { + QueryParManyUniqueIter { + world: self.world, + state: self.state, + entity_list: entities.into_iter().collect(), + last_run: self.last_run, + this_run: self.this_run, + batching_strategy: BatchingStrategy::new(), + } + } + /// Returns the read-only query item for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. @@ -1045,16 +1275,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`get_mut`](Self::get_mut) to get a mutable query item. #[inline] pub fn get(&self, entity: Entity) -> Result, QueryEntityError> { - // SAFETY: system runs without conflicts with other systems. - // same-system queries have runtime borrow checks when they conflict - unsafe { - self.state.as_readonly().get_unchecked_manual( - self.world, - entity, - self.last_run, - self.this_run, - ) - } + self.as_readonly().get_inner(entity) } /// Returns the read-only query items for the given array of [`Entity`]. @@ -1063,6 +1284,39 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. /// The elements of the array do not need to be unique, unlike `get_many_mut`. /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// use bevy_ecs::query::QueryEntityError; + /// + /// #[derive(Component, PartialEq, Debug)] + /// struct A(usize); + /// + /// let mut world = World::new(); + /// let entity_vec: Vec = (0..3).map(|i| world.spawn(A(i)).id()).collect(); + /// let entities: [Entity; 3] = entity_vec.try_into().unwrap(); + /// + /// world.spawn(A(73)); + /// + /// let mut query_state = world.query::<&A>(); + /// let query = query_state.query(&world); + /// + /// let component_values = query.get_many(entities).unwrap(); + /// + /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); + /// + /// let wrong_entity = Entity::from_raw(365); + /// + /// assert_eq!( + /// match query.get_many([wrong_entity]).unwrap_err() { + /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// _ => panic!(), + /// }, + /// wrong_entity + /// ); + /// ``` + /// /// # See also /// /// - [`get_many_mut`](Self::get_many_mut) to get mutable query items. @@ -1072,13 +1326,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { &self, entities: [Entity; N], ) -> Result<[ROQueryItem<'_, D>; N], QueryEntityError> { - // SAFETY: - // - `&self` ensures there is no mutable access to any components accessible to this query. - // - `self.world` matches `self.state`. - unsafe { - self.state - .get_many_read_only_manual(self.world, entities, self.last_run, self.this_run) - } + // Note that this calls `get_many_readonly` instead of `get_many_inner` + // since we don't need to check for duplicates. + self.as_readonly().get_many_readonly(entities) } /// Returns the read-only query items for the given array of [`Entity`]. @@ -1162,11 +1412,68 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`get`](Self::get) to get a read-only query item. #[inline] pub fn get_mut(&mut self, entity: Entity) -> Result, QueryEntityError> { + self.reborrow().get_inner(entity) + } + + /// Returns the query item for the given [`Entity`]. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// + /// This is always guaranteed to run in `O(1)` time. + /// + /// # See also + /// + /// - [`get_mut`](Self::get_mut) to get the item using a mutable borrow of the [`Query`]. + #[inline] + pub fn get_inner(self, entity: Entity) -> Result, QueryEntityError<'w>> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { - self.state - .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) + let location = self + .world + .entities() + .get(entity) + .ok_or(EntityDoesNotExistError::new(entity, self.world.entities()))?; + if !self + .state + .matched_archetypes + .contains(location.archetype_id.index()) + { + return Err(QueryEntityError::QueryDoesNotMatch(entity, self.world)); + } + let archetype = self + .world + .archetypes() + .get(location.archetype_id) + .debug_checked_unwrap(); + let mut fetch = D::init_fetch( + self.world, + &self.state.fetch_state, + self.last_run, + self.this_run, + ); + let mut filter = F::init_fetch( + self.world, + &self.state.filter_state, + self.last_run, + self.this_run, + ); + + let table = self + .world + .storages() + .tables + .get(location.table_id) + .debug_checked_unwrap(); + D::set_archetype(&mut fetch, &self.state.fetch_state, archetype, table); + F::set_archetype(&mut filter, &self.state.filter_state, archetype, table); + + if F::filter_fetch(&mut filter, entity, location.table_row) { + Ok(D::fetch(&mut fetch, entity, location.table_row)) + } else { + Err(QueryEntityError::QueryDoesNotMatch(entity, self.world)) + } } } @@ -1175,20 +1482,151 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// The returned query items are in the same order as the input. /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// use bevy_ecs::query::QueryEntityError; + /// + /// #[derive(Component, PartialEq, Debug)] + /// struct A(usize); + /// + /// let mut world = World::new(); + /// + /// let entities: Vec = (0..3).map(|i| world.spawn(A(i)).id()).collect(); + /// let entities: [Entity; 3] = entities.try_into().unwrap(); + /// + /// world.spawn(A(73)); + /// let wrong_entity = Entity::from_raw(57); + /// let invalid_entity = world.spawn_empty().id(); + /// + /// + /// let mut query_state = world.query::<&mut A>(); + /// let mut query = query_state.query_mut(&mut world); + /// + /// let mut mutable_component_values = query.get_many_mut(entities).unwrap(); + /// + /// for mut a in &mut mutable_component_values { + /// a.0 += 5; + /// } + /// + /// let component_values = query.get_many(entities).unwrap(); + /// + /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); + /// + /// assert_eq!( + /// match query + /// .get_many_mut([wrong_entity]) + /// .unwrap_err() + /// { + /// QueryEntityError::EntityDoesNotExist(error) => error.entity, + /// _ => panic!(), + /// }, + /// wrong_entity + /// ); + /// assert_eq!( + /// match query + /// .get_many_mut([invalid_entity]) + /// .unwrap_err() + /// { + /// QueryEntityError::QueryDoesNotMatch(entity, _) => entity, + /// _ => panic!(), + /// }, + /// invalid_entity + /// ); + /// assert_eq!( + /// query + /// .get_many_mut([entities[0], entities[0]]) + /// .unwrap_err(), + /// QueryEntityError::AliasedMutability(entities[0]) + /// ); + /// ``` /// # See also /// - /// - [`get_many`](Self::get_many) to get read-only query items. + /// - [`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, entities: [Entity; N], - ) -> Result<[D::Item<'_>; N], QueryEntityError> { - // SAFETY: scheduler ensures safe Query world access - unsafe { - self.state - .get_many_unchecked_manual(self.world, entities, self.last_run, self.this_run) + ) -> Result<[D::Item<'_>; N], QueryEntityError> { + self.reborrow().get_many_inner(entities) + } + + /// Returns the query items for the given array of [`Entity`]. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// The returned query items are in the same order as the input. + /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. + /// + /// # See also + /// + /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. + /// - [`get_many_mut`](Self::get_many_mut) to get items using a mutable reference. + /// - [`get_many_readonly`](Self::get_many_readonly) to get read-only query items without checking for duplicate entities + /// with the actual "inner" world lifetime. + #[inline] + pub fn get_many_inner( + self, + entities: [Entity; N], + ) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { + // Verify that all entities are unique + for i in 0..N { + for j in 0..i { + if entities[i] == entities[j] { + return Err(QueryEntityError::AliasedMutability(entities[i])); + } + } + } + + // SAFETY: All entities are unique, so the results don't alias. + unsafe { self.get_many_impl(entities) } + } + + /// Returns the query items for the given array of [`Entity`]. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// The returned query items are in the same order as the input. + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// + /// # See also + /// + /// - [`get_many`](Self::get_many) to get read-only query items without checking for duplicate entities. + /// - [`get_many_mut`](Self::get_many_mut) to get items using a mutable reference. + /// - [`get_many_inner`](Self::get_many_readonly) to get mutable query items with the actual "inner" world lifetime. + #[inline] + pub fn get_many_readonly( + self, + entities: [Entity; N], + ) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> + where + D: ReadOnlyQueryData, + { + // SAFETY: The query results are read-only, so they don't conflict if there are duplicate entities. + unsafe { self.get_many_impl(entities) } + } + + /// Returns the query items for the given array of [`Entity`]. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// # Safety + /// + /// The caller must ensure that the query data returned for the entities does not conflict, + /// either because they are all unique or because the data is read-only. + unsafe fn get_many_impl( + self, + entities: [Entity; N], + ) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { + let mut values = [(); N].map(|_| MaybeUninit::uninit()); + + for (value, entity) in core::iter::zip(&mut values, entities) { + // SAFETY: The caller asserts that the results don't alias + let item = unsafe { self.copy_unsafe() }.get_inner(entity)?; + *value = MaybeUninit::new(item); } + + // SAFETY: Each value has been fully initialized. + Ok(values.map(|x| unsafe { x.assume_init() })) } /// Returns the query items for the given array of [`Entity`]. @@ -1264,12 +1702,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`get_mut`](Self::get_mut) for the safe version. #[inline] pub unsafe fn get_unchecked(&self, entity: Entity) -> Result, QueryEntityError> { - // SEMI-SAFETY: system runs without conflicts with other systems. - // same-system queries have runtime borrow checks when they conflict - unsafe { - self.state - .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) - } + // SAFETY: The caller promises that this will not result in multiple mutable references. + unsafe { self.reborrow_unsafe() }.get_inner(entity) } /// Returns a single read-only query item when there is exactly one entity matching the query. @@ -1335,16 +1769,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`single`](Self::single) for the panicking version. #[inline] pub fn get_single(&self) -> Result, QuerySingleError> { - // SAFETY: - // the query ensures that the components it accesses are not mutably accessible somewhere else - // and the query is read only. - unsafe { - self.state.as_readonly().get_single_unchecked_manual( - self.world, - self.last_run, - self.this_run, - ) - } + self.as_readonly().get_single_inner() } /// Returns a single query item when there is exactly one entity matching the query. @@ -1406,12 +1831,82 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// - [`single_mut`](Self::single_mut) for the panicking version. #[inline] pub fn get_single_mut(&mut self) -> Result, QuerySingleError> { - // SAFETY: - // the query ensures mutable access to the components it accesses, and the query - // is uniquely borrowed - unsafe { - self.state - .get_single_unchecked_manual(self.world, self.last_run, self.this_run) + self.reborrow().get_single_inner() + } + + /// 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. + /// + /// # Panics + /// + /// This method panics if the number of query items is **not** exactly one. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct Player; + /// # #[derive(Component)] + /// # struct Health(u32); + /// # + /// fn regenerate_player_health_system(query: Query<&mut Health, With>) { + /// let mut health = query.single_inner(); + /// health.0 += 1; + /// } + /// # bevy_ecs::system::assert_is_system(regenerate_player_health_system); + /// ``` + /// + /// # See also + /// + /// - [`get_single_inner`](Self::get_single_inner) for the non-panicking version. + /// - [`single`](Self::single) to get the read-only query item. + /// - [`single_mut`](Self::single_mut) to get the mutable query item. + #[track_caller] + pub fn single_inner(self) -> D::Item<'w> { + self.get_single_inner().unwrap() + } + + /// 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. + /// + /// If the number of query items is not exactly one, a [`QuerySingleError`] is returned instead. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # #[derive(Component)] + /// # struct Player; + /// # #[derive(Component)] + /// # struct Health(u32); + /// # + /// fn regenerate_player_health_system(query: Query<&mut Health, With>) { + /// let mut health = query.get_single_inner().expect("Error: Could not find a single player."); + /// health.0 += 1; + /// } + /// # bevy_ecs::system::assert_is_system(regenerate_player_health_system); + /// ``` + /// + /// # See also + /// + /// - [`get_single`](Self::get_single) to get the read-only query item. + /// - [`get_single_mut`](Self::get_single_mut) to get the mutable query item. + /// - [`single_inner`](Self::single_inner) for the panicking version. + #[inline] + pub fn get_single_inner(self) -> Result, QuerySingleError> { + let mut query = self.into_iter(); + let first = query.next(); + let extra = query.next().is_some(); + + match (first, extra) { + (Some(r), false) => Ok(r), + (None, _) => Err(QuerySingleError::NoEntities(core::any::type_name::())), + (Some(_), _) => Err(QuerySingleError::MultipleEntities(core::any::type_name::< + Self, + >())), } } @@ -1445,14 +1940,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// [`Changed`]: crate::query::Changed #[inline] pub fn is_empty(&self) -> bool { - // SAFETY: - // - `self.world` has permission to read any data required by the WorldQuery. - // - `&self` ensures that no one currently has write access. - // - `self.world` matches `self.state`. - unsafe { - self.state - .is_empty_unsafe_world_cell(self.world, self.last_run, self.this_run) - } + self.as_nop().iter().next().is_none() } /// Returns `true` if the given [`Entity`] matches the query. @@ -1481,13 +1969,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// ``` #[inline] pub fn contains(&self, entity: Entity) -> bool { - // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access - unsafe { - self.state - .as_nop() - .get_unchecked_manual(self.world, entity, self.last_run, self.this_run) - .is_ok() - } + self.as_nop().get(entity).is_ok() } /// Returns a [`QueryLens`] that can be used to get a query with a more general fetch. @@ -1648,6 +2130,80 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.transmute_lens_filtered::() } + /// Returns a [`QueryLens`] that can be used to get a query with a more general fetch. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// 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`] + /// + /// ## Panics + /// + /// This will panic if `NewD` is not a subset of the original fetch `Q` + /// + /// ## Example + /// + /// ```rust + /// # use bevy_ecs::prelude::*; + /// # use bevy_ecs::system::QueryLens; + /// # + /// # #[derive(Component)] + /// # struct A(usize); + /// # + /// # #[derive(Component)] + /// # struct B(usize); + /// # + /// # let mut world = World::new(); + /// # + /// # world.spawn((A(10), B(5))); + /// # + /// fn reusable_function(mut lens: QueryLens<&A>) { + /// assert_eq!(lens.query().single().0, 10); + /// } + /// + /// // We can use the function in a system that takes the exact query. + /// fn system_1(query: Query<&A>) { + /// reusable_function(query.into_query_lens()); + /// } + /// + /// // We can also use it with a query that does not match exactly + /// // by transmuting it. + /// fn system_2(query: Query<(&mut A, &B)>) { + /// let mut lens = query.transmute_lens_inner::<&A>(); + /// reusable_function(lens); + /// } + /// + /// # let mut schedule = Schedule::default(); + /// # schedule.add_systems((system_1, system_2)); + /// # schedule.run(&mut world); + /// ``` + /// + /// ## Allowed Transmutes + /// + /// Besides removing parameters from the query, you can also + /// make limited changes to the types of parameters. + /// + /// * Can always add/remove [`Entity`] + /// * Can always add/remove [`EntityLocation`] + /// * Can always add/remove [`&Archetype`] + /// * `Ref` <-> `&T` + /// * `&mut T` -> `&T` + /// * `&mut T` -> `Ref` + /// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef) + /// + /// [`EntityLocation`]: crate::entity::EntityLocation + /// [`&Archetype`]: crate::archetype::Archetype + /// + /// # See also + /// + /// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`]. + #[track_caller] + pub fn transmute_lens_inner(self) -> QueryLens<'w, NewD> { + self.transmute_lens_filtered_inner::() + } + /// Equivalent to [`Self::transmute_lens`] but also includes a [`QueryFilter`] type. /// /// Note that the lens will iterate the same tables and archetypes as the original query. This means that @@ -1658,6 +2214,24 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { pub fn transmute_lens_filtered( &mut self, ) -> QueryLens<'_, NewD, NewF> { + self.reborrow().transmute_lens_filtered_inner() + } + + /// Equivalent to [`Self::transmute_lens_inner`] but also includes a [`QueryFilter`] type. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// 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. + /// + /// # See also + /// + /// - [`transmute_lens_filtered`](Self::transmute_lens_filtered) to convert to a lens using a mutable borrow of the [`Query`]. + #[track_caller] + pub fn transmute_lens_filtered_inner( + self, + ) -> QueryLens<'w, NewD, NewF> { let state = self.state.transmute_filtered::(self.world); QueryLens { world: self.world, @@ -1672,6 +2246,15 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.transmute_lens() } + /// Gets a [`QueryLens`] with the same accesses as the existing query + /// + /// # See also + /// + /// - [`as_query_lens`](Self::as_query_lens) to convert to a lens using a mutable borrow of the [`Query`]. + pub fn into_query_lens(self) -> QueryLens<'w, D> { + self.transmute_lens_inner() + } + /// Returns a [`QueryLens`] that can be used to get a query with the combined fetch. /// /// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`. @@ -1733,6 +2316,33 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { self.join_filtered(other) } + /// Returns a [`QueryLens`] that can be used to get a query with the combined fetch. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// 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. + /// To maintain or change filter terms see `Self::join_filtered`. + /// + /// ## Panics + /// + /// This will panic if `NewD` is not a subset of the union of the original fetch `Q` and `OtherD`. + /// + /// ## Allowed Transmutes + /// + /// Like `transmute_lens` the query terms can be changed with some restrictions. + /// See [`Self::transmute_lens`] for more details. + /// + /// # See also + /// + /// - [`join`](Self::join) to join using a mutable borrow of the [`Query`]. + pub fn join_inner( + self, + other: &mut Query, + ) -> QueryLens<'w, NewD> { + self.join_filtered_inner(other) + } + /// Equivalent to [`Self::join`] but also includes a [`QueryFilter`] type. /// /// Note that the lens with iterate a subset of the original queries' tables @@ -1749,6 +2359,30 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { &mut self, other: &mut Query, ) -> QueryLens<'_, NewD, NewF> { + self.reborrow().join_filtered_inner(other) + } + + /// Equivalent to [`Self::join_inner`] but also includes a [`QueryFilter`] type. + /// This consumes the [`Query`] to return results with the actual "inner" world lifetime. + /// + /// 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. + /// + /// # See also + /// + /// - [`join_filtered`](Self::join_filtered) to join using a mutable borrow of the [`Query`]. + pub fn join_filtered_inner< + OtherD: QueryData, + OtherF: QueryFilter, + NewD: QueryData, + NewF: QueryFilter, + >( + self, + other: &mut Query, + ) -> QueryLens<'w, NewD, NewF> { let state = self .state .join_filtered::(self.world, other.state); @@ -1761,6 +2395,19 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { } } +impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { + type Item = D::Item<'w>; + type IntoIter = QueryIter<'w, 's, D, F>; + + fn into_iter(self) -> Self::IntoIter { + // SAFETY: + // - `self.world` has permission to access the required components. + // - We consume the query, so mutable queries cannot alias. + // Read-only queries are `Copy`, but may alias themselves. + unsafe { QueryIter::new(self.world, self.state, self.last_run, self.this_run) } + } +} + impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w Query<'_, 's, D, F> { type Item = ROQueryItem<'w, D>; type IntoIter = QueryIter<'w, 's, D::ReadOnly, F>; @@ -1780,52 +2427,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'w mut Query<'_, 's } impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { - /// Returns the query item for the given [`Entity`], with the actual "inner" world lifetime. - /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is - /// returned instead. - /// - /// This can only return immutable data (mutable data will be cast to an immutable form). - /// See [`get_mut`](Self::get_mut) for queries that contain at least one mutable component. - /// - /// # Example - /// - /// Here, `get` is used to retrieve the exact query item of the entity specified by the - /// `SelectedCharacter` resource. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Resource)] - /// # struct SelectedCharacter { entity: Entity } - /// # #[derive(Component)] - /// # struct Character { name: String } - /// # - /// fn print_selected_character_name_system( - /// query: Query<&Character>, - /// selection: Res - /// ) - /// { - /// if let Ok(selected_character) = query.get(selection.entity) { - /// println!("{}", selected_character.name); - /// } - /// } - /// # bevy_ecs::system::assert_is_system(print_selected_character_name_system); - /// ``` - #[inline] - pub fn get_inner(&self, entity: Entity) -> Result, QueryEntityError> { - // SAFETY: system runs without conflicts with other systems. - // same-system queries have runtime borrow checks when they conflict - unsafe { - self.state.as_readonly().get_unchecked_manual( - self.world, - entity, - self.last_run, - self.this_run, - ) - } - } - /// Returns an [`Iterator`] over the query items, with the actual "inner" world lifetime. /// /// This can only return immutable data (mutable data will be cast to an immutable form). @@ -1851,13 +2452,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter> Query<'w, 's, D, F> { /// ``` #[inline] pub fn iter_inner(&self) -> QueryIter<'w, 's, D::ReadOnly, F> { - // SAFETY: system runs without conflicts with other systems. - // same-system queries have runtime borrow checks when they conflict - unsafe { - self.state - .as_readonly() - .iter_unchecked_manual(self.world, self.last_run, self.this_run) - } + (*self).into_iter() } } @@ -1873,7 +2468,21 @@ pub struct QueryLens<'w, Q: QueryData, F: QueryFilter = ()> { impl<'w, Q: QueryData, F: QueryFilter> QueryLens<'w, Q, F> { /// Create a [`Query`] from the underlying [`QueryState`]. - pub fn query(&mut self) -> Query<'w, '_, Q, F> { + pub fn query(&mut self) -> Query<'_, '_, Q, F> { + Query { + world: self.world, + state: &self.state, + last_run: self.last_run, + this_run: self.this_run, + } + } +} + +impl<'w, Q: ReadOnlyQueryData, F: QueryFilter> QueryLens<'w, Q, F> { + /// Create a [`Query`] from the underlying [`QueryState`]. + /// This returns results with the actual "inner" world lifetime, + /// so it may only be used with read-only queries to prevent mutable aliasing. + pub fn query_inner(&self) -> Query<'w, '_, Q, F> { Query { world: self.world, state: &self.state, @@ -1884,9 +2493,9 @@ impl<'w, Q: QueryData, F: QueryFilter> QueryLens<'w, Q, F> { } impl<'w, 's, Q: QueryData, F: QueryFilter> From<&'s mut QueryLens<'w, Q, F>> - for Query<'w, 's, Q, F> + for Query<'s, 's, Q, F> { - fn from(value: &'s mut QueryLens<'w, Q, F>) -> Query<'w, 's, Q, F> { + fn from(value: &'s mut QueryLens<'w, Q, F>) -> Query<'s, 's, Q, F> { value.query() } } @@ -1969,3 +2578,51 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Populated<'w, 's, D, F> { self.0 } } + +#[cfg(test)] +mod tests { + use crate::{prelude::*, query::QueryEntityError}; + use alloc::vec::Vec; + + #[test] + fn get_many_uniqueness() { + let mut world = World::new(); + + let entities: Vec = (0..10).map(|_| world.spawn_empty().id()).collect(); + + let mut query_state = world.query::(); + + // It's best to test get_many_inner directly, as it is shared + // We don't care about aliased mutability for the read-only equivalent + + // SAFETY: Query does not access world data. + assert!(query_state + .query_mut(&mut world) + .get_many_inner::<10>(entities.clone().try_into().unwrap()) + .is_ok()); + + assert_eq!( + query_state + .query_mut(&mut world) + .get_many_inner([entities[0], entities[0]]) + .unwrap_err(), + QueryEntityError::AliasedMutability(entities[0]) + ); + + assert_eq!( + query_state + .query_mut(&mut world) + .get_many_inner([entities[0], entities[1], entities[0]]) + .unwrap_err(), + QueryEntityError::AliasedMutability(entities[0]) + ); + + assert_eq!( + query_state + .query_mut(&mut world) + .get_many_inner([entities[9], entities[9]]) + .unwrap_err(), + QueryEntityError::AliasedMutability(entities[9]) + ); + } +} diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 4f22340bff8cc..d0990e907f8ec 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -381,7 +381,6 @@ impl Debug for RunSystemError { #[cfg(test)] mod tests { use super::*; - use crate as bevy_ecs; use crate::prelude::*; #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 5859b48346d89..8cf3cfb3023f6 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -2,7 +2,7 @@ pub use crate::change_detection::{NonSendMut, Res, ResMut}; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, - change_detection::{Ticks, TicksMut}, + change_detection::{MaybeLocation, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick}, entity::Entities, query::{ @@ -21,13 +21,12 @@ use alloc::{borrow::ToOwned, boxed::Box, vec::Vec}; pub use bevy_ecs_macros::SystemParam; use bevy_ptr::UnsafeCellDeref; use bevy_utils::synccell::SyncCell; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ any::Any, fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, + panic::Location, }; use disqualified::ShortName; @@ -334,7 +333,8 @@ unsafe impl SystemParam for Qu // SAFETY: We have registered all of the query's world accesses, // so the caller ensures that `world` has permission to access any // world data that the query needs. - unsafe { Query::new(world, state, system_meta.last_run, change_tick) } + // 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) } } } @@ -402,12 +402,14 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo 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 somewhere elsewhere. - let result = - unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) }; - let single = - result.expect("The query was expected to contain exactly one matching entity."); + // 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 single = query + .get_single_inner() + .expect("The query was expected to contain exactly one matching entity."); Single { item: single, _filter: PhantomData, @@ -420,17 +422,17 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> bool { - state.validate_world(world.id()); // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere // and the query is read only. - let result = unsafe { - state.as_readonly().get_single_unchecked_manual( + // 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 = result.is_ok(); + let is_valid = query.get_single_inner().is_ok(); if !is_valid { system_meta.try_warn_param::(); } @@ -468,9 +470,11 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam ) -> Self::Item<'w, 's> { state.validate_world(world.id()); // SAFETY: State ensures that the components it accesses are not accessible elsewhere. - let result = - unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) }; - match result { + // 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) + }; + match query.get_single_inner() { Ok(single) => Some(Single { item: single, _filter: PhantomData, @@ -486,16 +490,17 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> bool { - state.validate_world(world.id()); // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere // and the query is read only. - let result = unsafe { - state.as_readonly().get_single_unchecked_manual( + // 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.get_single_inner(); let is_valid = !matches!(result, Err(QuerySingleError::MultipleEntities(_))); if !is_valid { system_meta.try_warn_param::(); @@ -555,13 +560,17 @@ unsafe impl SystemParam system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> bool { - state.validate_world(world.id()); // SAFETY: // - We have read-only access to the components accessed by query. - // - The world has been validated. - !unsafe { - state.is_empty_unsafe_world_cell(world, system_meta.last_run, world.change_tick()) - } + // - 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(), + ) + }; + !query.is_empty() } } @@ -676,7 +685,7 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re /// // ... /// # let _event = event; /// } -/// set.p1().send(MyEvent::new()); +/// set.p1().write(MyEvent::new()); /// /// let entities = set.p2().entities(); /// // ... @@ -853,7 +862,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - let (ptr, ticks, _caller) = + let (ptr, ticks, caller) = world .get_resource_with_ticks(component_id) .unwrap_or_else(|| { @@ -871,8 +880,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { last_run: system_meta.last_run, this_run: change_tick, }, - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), } } } @@ -898,7 +906,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { ) -> Self::Item<'w, 's> { world .get_resource_with_ticks(component_id) - .map(|(ptr, ticks, _caller)| Res { + .map(|(ptr, ticks, caller)| Res { value: ptr.deref(), ticks: Ticks { added: ticks.added.deref(), @@ -906,8 +914,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { last_run: system_meta.last_run, this_run: change_tick, }, - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), }) } } @@ -984,7 +991,6 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { last_run: system_meta.last_run, this_run: change_tick, }, - #[cfg(feature = "track_location")] changed_by: value.changed_by, } } @@ -1016,7 +1022,6 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { last_run: system_meta.last_run, this_run: change_tick, }, - #[cfg(feature = "track_location")] changed_by: value.changed_by, }) } @@ -1125,6 +1130,25 @@ unsafe impl<'w> SystemParam for DeferredWorld<'w> { /// assert_eq!(read_system.run((), world), 0); /// ``` /// +/// A simple way to set a different default value for a local is by wrapping the value with an Option. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # let world = &mut World::default(); +/// fn counter_from_10(mut count: Local>) -> usize { +/// let count = count.get_or_insert(10); +/// *count += 1; +/// *count +/// } +/// let mut counter_system = IntoSystem::into_system(counter_from_10); +/// counter_system.initialize(world); +/// +/// // Counter is initialized at 10, and increases to 11 on first run. +/// assert_eq!(counter_system.run((), world), 11); +/// // Counter is only increased by 1 on subsequent runs. +/// assert_eq!(counter_system.run((), world), 12); +/// ``` +/// /// N.B. A [`Local`]s value cannot be read or written to outside of the containing system. /// To add configuration to a system, convert a capturing closure into the system instead: /// @@ -1412,8 +1436,7 @@ pub struct NonSend<'w, T: 'static> { ticks: ComponentTicks, last_run: Tick, this_run: Tick, - #[cfg(feature = "track_location")] - changed_by: &'static Location<'static>, + changed_by: MaybeLocation<&'w &'static Location<'static>>, } // SAFETY: Only reads a single World non-send resource @@ -1440,9 +1463,8 @@ impl<'w, T: 'static> NonSend<'w, T> { } /// The location that last caused this to change. - #[cfg(feature = "track_location")] - pub fn changed_by(&self) -> &'static Location<'static> { - self.changed_by + pub fn changed_by(&self) -> MaybeLocation { + self.changed_by.copied() } } @@ -1463,8 +1485,7 @@ impl<'a, T> From> for NonSend<'a, T> { }, this_run: nsm.ticks.this_run, last_run: nsm.ticks.last_run, - #[cfg(feature = "track_location")] - changed_by: nsm.changed_by, + changed_by: nsm.changed_by.map(|changed_by| &*changed_by), } } } @@ -1523,7 +1544,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - let (ptr, ticks, _caller) = + let (ptr, ticks, caller) = world .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { @@ -1539,8 +1560,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { ticks: ticks.read(), last_run: system_meta.last_run, this_run: change_tick, - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), } } } @@ -1566,13 +1586,12 @@ unsafe impl SystemParam for Option> { ) -> Self::Item<'w, 's> { world .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks, _caller)| NonSend { + .map(|(ptr, ticks, caller)| NonSend { value: ptr.deref(), ticks: ticks.read(), last_run: system_meta.last_run, this_run: change_tick, - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), }) } } @@ -1634,7 +1653,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - let (ptr, ticks, _caller) = + let (ptr, ticks, caller) = world .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { @@ -1647,8 +1666,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { NonSendMut { value: ptr.assert_unique().deref_mut(), ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - #[cfg(feature = "track_location")] - changed_by: _caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), } } } @@ -1671,11 +1689,10 @@ unsafe impl<'a, T: 'static> SystemParam for Option> { ) -> Self::Item<'w, 's> { world .get_non_send_with_ticks(component_id) - .map(|(ptr, ticks, _caller)| NonSendMut { + .map(|(ptr, ticks, caller)| NonSendMut { value: ptr.assert_unique().deref_mut(), ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick), - #[cfg(feature = "track_location")] - changed_by: _caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), }) } } @@ -2578,10 +2595,7 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { #[cfg(test)] mod tests { use super::*; - use crate::{ - self as bevy_ecs, // Necessary for the `SystemParam` Derive when used inside `bevy_ecs`. - system::assert_is_system, - }; + use crate::system::assert_is_system; use core::cell::RefCell; // Compile test for https://github.com/bevyengine/bevy/pull/2838. diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 2a85afd12adad..2d849dda95666 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -1,7 +1,6 @@ #[cfg(feature = "bevy_reflect")] use crate::reflect::ReflectComponent; use crate::{ - self as bevy_ecs, change_detection::Mut, entity::Entity, system::{input::SystemInput, BoxedSystem, IntoSystem, System}, @@ -498,7 +497,6 @@ impl core::fmt::Debug for RegisteredSystemError { mod tests { use crate::prelude::*; - use crate::{self as bevy_ecs}; #[derive(Resource, Default, PartialEq, Debug)] struct Counter(u8); diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index f1563643fcc8c..e8f820c0661f4 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -335,7 +335,7 @@ impl SystemBuffer for CommandQueue { #[cfg(test)] mod test { use super::*; - use crate::{self as bevy_ecs, resource::Resource}; + use crate::resource::Resource; use alloc::{borrow::ToOwned, string::String, sync::Arc}; use core::{ panic::AssertUnwindSafe, diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 2be76b1fc0f90..ea2899c5f916a 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -1,7 +1,6 @@ //! Internal components used by bevy with a fixed component id. //! Constants are used to skip [`TypeId`] lookups in hot paths. use super::*; -use crate::{self as bevy_ecs}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 35b38fd1988f0..654d35266e29a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -1,10 +1,8 @@ use core::ops::Deref; -#[cfg(feature = "track_location")] -use core::panic::Location; use crate::{ archetype::Archetype, - change_detection::MutUntyped, + change_detection::{MaybeLocation, MutUntyped}, component::{ComponentId, HookContext, Mutable}, entity::Entity, event::{Event, EventId, Events, SendBatchIds}, @@ -14,7 +12,7 @@ use crate::{ resource::Resource, system::{Commands, Query}, traversal::Traversal, - world::{error::EntityFetchError, WorldEntityFetch}, + world::{error::EntityMutableFetchError, WorldEntityFetch}, }; use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLACE}; @@ -96,24 +94,13 @@ impl<'w> DeferredWorld<'w> { &mut self, entity: Entity, f: impl FnOnce(&mut T) -> R, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // If the component is not registered, then it doesn't exist on this entity, so no action required. let Some(component_id) = self.component_id::() else { return Ok(None); }; - let entity_cell = match self.get_entity_mut(entity) { - Ok(cell) => cell, - Err(EntityFetchError::AliasedMutability(..)) => { - return Err(EntityFetchError::AliasedMutability(entity)) - } - Err(EntityFetchError::NoSuchEntity(..)) => { - return Err(EntityFetchError::NoSuchEntity( - entity, - self.entities().entity_does_not_exist_error_details(entity), - )) - } - }; + let entity_cell = self.get_entity_mut(entity)?; if !entity_cell.contains::() { return Ok(None); @@ -132,16 +119,14 @@ impl<'w> DeferredWorld<'w> { archetype, entity, [component_id].into_iter(), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); if archetype.has_replace_observer() { self.trigger_observers( ON_REPLACE, entity, [component_id].into_iter(), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); } } @@ -173,16 +158,14 @@ impl<'w> DeferredWorld<'w> { archetype, entity, [component_id].into_iter(), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); if archetype.has_insert_observer() { self.trigger_observers( ON_INSERT, entity, [component_id].into_iter(), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); } } @@ -207,9 +190,9 @@ impl<'w> DeferredWorld<'w> { /// /// # Errors /// - /// - Returns [`EntityFetchError::NoSuchEntity`] if any of the given `entities` do not exist in the world. + /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. /// - Only the first entity found to be missing will be returned. - /// - Returns [`EntityFetchError::AliasedMutability`] if the same entity is requested multiple times. + /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times. /// /// # Examples /// @@ -223,7 +206,7 @@ impl<'w> DeferredWorld<'w> { pub fn get_entity_mut( &mut self, entities: F, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { let cell = self.as_unsafe_world_cell(); // SAFETY: `&mut self` gives mutable access to the entire world, // and prevents any other access to the world. @@ -366,18 +349,8 @@ impl<'w> DeferredWorld<'w> { &mut self, state: &'s mut QueryState, ) -> Query<'_, 's, D, F> { - state.validate_world(self.world.id()); - state.update_archetypes(self); - // SAFETY: We ran validate_world to ensure our state matches - unsafe { - let world_cell = self.world; - Query::new( - world_cell, - state, - world_cell.last_change_tick(), - world_cell.change_tick(), - ) - } + // SAFETY: We have mutable access to the entire world + unsafe { state.query_unchecked(self.world) } } /// Gets a mutable reference to the resource of the given type @@ -529,7 +502,7 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_add_hook() { for component_id in targets { @@ -541,10 +514,7 @@ impl<'w> DeferredWorld<'w> { HookContext { entity, component_id, - #[cfg(feature = "track_location")] - caller: Some(caller), - #[cfg(not(feature = "track_location"))] - caller: None, + caller, }, ); } @@ -562,7 +532,7 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_insert_hook() { for component_id in targets { @@ -574,10 +544,7 @@ impl<'w> DeferredWorld<'w> { HookContext { entity, component_id, - #[cfg(feature = "track_location")] - caller: Some(caller), - #[cfg(not(feature = "track_location"))] - caller: None, + caller, }, ); } @@ -595,7 +562,7 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_replace_hook() { for component_id in targets { @@ -607,10 +574,7 @@ impl<'w> DeferredWorld<'w> { HookContext { entity, component_id, - #[cfg(feature = "track_location")] - caller: Some(caller), - #[cfg(not(feature = "track_location"))] - caller: None, + caller, }, ); } @@ -628,7 +592,7 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_remove_hook() { for component_id in targets { @@ -640,10 +604,7 @@ impl<'w> DeferredWorld<'w> { HookContext { entity, component_id, - #[cfg(feature = "track_location")] - caller: Some(caller), - #[cfg(not(feature = "track_location"))] - caller: None, + caller, }, ); } @@ -661,7 +622,7 @@ impl<'w> DeferredWorld<'w> { archetype: &Archetype, entity: Entity, targets: impl Iterator, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_despawn_hook() { for component_id in targets { @@ -673,10 +634,7 @@ impl<'w> DeferredWorld<'w> { HookContext { entity, component_id, - #[cfg(feature = "track_location")] - caller: Some(caller), - #[cfg(not(feature = "track_location"))] - caller: None, + caller, }, ); } @@ -694,7 +652,7 @@ impl<'w> DeferredWorld<'w> { event: ComponentId, target: Entity, components: impl Iterator + Clone, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { Observers::invoke::<_>( self.reborrow(), @@ -703,7 +661,6 @@ impl<'w> DeferredWorld<'w> { components, &mut (), &mut false, - #[cfg(feature = "track_location")] caller, ); } @@ -720,7 +677,7 @@ impl<'w> DeferredWorld<'w> { components: &[ComponentId], data: &mut E, mut propagate: bool, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) where T: Traversal, { @@ -732,7 +689,6 @@ impl<'w> DeferredWorld<'w> { components.iter().copied(), data, &mut propagate, - #[cfg(feature = "track_location")] caller, ); if !propagate { diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 466dcc31d01e2..e731582f48b85 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -2,9 +2,9 @@ use alloc::vec::Vec; use core::mem::MaybeUninit; use crate::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity}, + entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity, EntityDoesNotExistError}, world::{ - error::EntityFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, + error::EntityMutableFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, EntityWorldMut, }, }; @@ -56,8 +56,11 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`Entity`] if the entity does not exist. - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity>; + /// - Returns [`EntityDoesNotExistError`] if the entity does not exist. + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError>; /// Returns mutable reference(s) to the entities with the given [`Entity`] /// IDs, as determined by `self`. @@ -70,11 +73,13 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`EntityFetchError::NoSuchEntity`] if the entity does not exist. - /// - Returns [`EntityFetchError::AliasedMutability`] if the entity was + /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist. + /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was /// requested mutably more than once. - unsafe fn fetch_mut(self, cell: UnsafeWorldCell<'_>) - -> Result, EntityFetchError>; + unsafe fn fetch_mut( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityMutableFetchError>; /// Returns mutable reference(s) to the entities with the given [`Entity`] /// IDs, as determined by `self`, but without structural mutability. @@ -91,13 +96,13 @@ pub unsafe trait WorldEntityFetch { /// /// # Errors /// - /// - Returns [`EntityFetchError::NoSuchEntity`] if the entity does not exist. - /// - Returns [`EntityFetchError::AliasedMutability`] if the entity was + /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if the entity does not exist. + /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the entity was /// requested mutably more than once. unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError>; + ) -> Result, EntityMutableFetchError>; } // SAFETY: @@ -109,8 +114,11 @@ unsafe impl WorldEntityFetch for Entity { type Mut<'w> = EntityWorldMut<'w>; type DeferredMut<'w> = EntityMut<'w>; - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { - let ecell = cell.get_entity(self).ok_or(self)?; + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError> { + let ecell = cell.get_entity(self)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. Ok(unsafe { EntityRef::new(ecell) }) } @@ -118,14 +126,11 @@ unsafe impl WorldEntityFetch for Entity { unsafe fn fetch_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { let location = cell .entities() .get(self) - .ok_or(EntityFetchError::NoSuchEntity( - self, - cell.entities().entity_does_not_exist_error_details(self), - ))?; + .ok_or(EntityDoesNotExistError::new(self, cell.entities()))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. let world = unsafe { cell.world_mut() }; // SAFETY: location was fetched from the same world's `Entities`. @@ -135,11 +140,8 @@ unsafe impl WorldEntityFetch for Entity { unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { - let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity( - self, - cell.entities().entity_does_not_exist_error_details(self), - ))?; + ) -> Result, EntityMutableFetchError> { + let ecell = cell.get_entity(self)?; // SAFETY: caller ensures that the world cell has mutable access to the entity. Ok(unsafe { EntityMut::new(ecell) }) } @@ -154,21 +156,24 @@ unsafe impl WorldEntityFetch for [Entity; N] { type Mut<'w> = [EntityMut<'w>; N]; type DeferredMut<'w> = [EntityMut<'w>; N]; - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError> { <&Self>::fetch_ref(&self, cell) } unsafe fn fetch_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { <&Self>::fetch_mut(&self, cell) } unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { <&Self>::fetch_deferred_mut(&self, cell) } } @@ -182,10 +187,13 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { type Mut<'w> = [EntityMut<'w>; N]; type DeferredMut<'w> = [EntityMut<'w>; N]; - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError> { let mut refs = [MaybeUninit::uninit(); N]; for (r, &id) in core::iter::zip(&mut refs, self) { - let ecell = cell.get_entity(id).ok_or(id)?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. *r = MaybeUninit::new(unsafe { EntityRef::new(ecell) }); } @@ -199,22 +207,19 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { unsafe fn fetch_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // Check for duplicate entities. for i in 0..self.len() { for j in 0..i { if self[i] == self[j] { - return Err(EntityFetchError::AliasedMutability(self[i])); + return Err(EntityMutableFetchError::AliasedMutability(self[i])); } } } let mut refs = [const { MaybeUninit::uninit() }; N]; for (r, &id) in core::iter::zip(&mut refs, self) { - let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( - id, - cell.entities().entity_does_not_exist_error_details(id), - ))?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has mutable access to the entity. *r = MaybeUninit::new(unsafe { EntityMut::new(ecell) }); } @@ -228,7 +233,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // SAFETY: caller ensures that the world cell has mutable access to the entity, // and `fetch_mut` does not return structurally-mutable references. unsafe { self.fetch_mut(cell) } @@ -244,10 +249,13 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { type Mut<'w> = Vec>; type DeferredMut<'w> = Vec>; - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError> { let mut refs = Vec::with_capacity(self.len()); for &id in self { - let ecell = cell.get_entity(id).ok_or(id)?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. refs.push(unsafe { EntityRef::new(ecell) }); } @@ -258,22 +266,19 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { unsafe fn fetch_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // Check for duplicate entities. for i in 0..self.len() { for j in 0..i { if self[i] == self[j] { - return Err(EntityFetchError::AliasedMutability(self[i])); + return Err(EntityMutableFetchError::AliasedMutability(self[i])); } } } let mut refs = Vec::with_capacity(self.len()); for &id in self { - let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( - id, - cell.entities().entity_does_not_exist_error_details(id), - ))?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.push(unsafe { EntityMut::new(ecell) }); } @@ -284,7 +289,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // SAFETY: caller ensures that the world cell has mutable access to the entity, // and `fetch_mut` does not return structurally-mutable references. unsafe { self.fetch_mut(cell) } @@ -300,10 +305,13 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { type Mut<'w> = EntityHashMap>; type DeferredMut<'w> = EntityHashMap>; - unsafe fn fetch_ref(self, cell: UnsafeWorldCell<'_>) -> Result, Entity> { + unsafe fn fetch_ref( + self, + cell: UnsafeWorldCell<'_>, + ) -> Result, EntityDoesNotExistError> { let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { - let ecell = cell.get_entity(id).ok_or(id)?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has read-only access to the entity. refs.insert(id, unsafe { EntityRef::new(ecell) }); } @@ -313,13 +321,10 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { unsafe fn fetch_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { let mut refs = EntityHashMap::with_capacity(self.len()); for &id in self { - let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( - id, - cell.entities().entity_does_not_exist_error_details(id), - ))?; + let ecell = cell.get_entity(id)?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.insert(id, unsafe { EntityMut::new(ecell) }); } @@ -329,7 +334,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { unsafe fn fetch_deferred_mut( self, cell: UnsafeWorldCell<'_>, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { // SAFETY: caller ensures that the world cell has mutable access to the entity, // and `fetch_mut` does not return structurally-mutable references. unsafe { self.fetch_mut(cell) } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 912f4a968606f..17367e56df70f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,10 +1,14 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, - bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode}, - change_detection::MutUntyped, + bundle::{ + Bundle, BundleEffect, BundleFromComponents, BundleId, BundleInfo, BundleInserter, + DynamicBundle, InsertMode, + }, + change_detection::{MaybeLocation, MutUntyped}, component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType}, entity::{ - Entities, Entity, EntityBorrow, EntityCloneBuilder, EntityLocation, TrustedEntityBorrow, + Entities, Entity, EntityBorrow, EntityCloner, EntityClonerBuilder, EntityLocation, + TrustedEntityBorrow, }, event::Event, observer::Observer, @@ -21,8 +25,6 @@ use crate::{ use alloc::vec::Vec; use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_ptr::{OwningPtr, Ptr}; -#[cfg(feature = "track_location")] -use core::panic::Location; use core::{ any::TypeId, cmp::Ordering, @@ -290,8 +292,7 @@ impl<'w> EntityRef<'w> { } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.cell.spawned_by() } } @@ -881,8 +882,7 @@ impl<'w> EntityMut<'w> { } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.cell.spawned_by() } } @@ -1526,12 +1526,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn insert(&mut self, bundle: T) -> &mut Self { - self.insert_with_caller( - bundle, - InsertMode::Replace, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.insert_with_caller(bundle, InsertMode::Replace, MaybeLocation::caller()) } /// Adds a [`Bundle`] of components to the entity without overwriting. @@ -1544,12 +1539,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn insert_if_new(&mut self, bundle: T) -> &mut Self { - self.insert_with_caller( - bundle, - InsertMode::Keep, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.insert_with_caller(bundle, InsertMode::Keep, MaybeLocation::caller()) } /// Split into a new function so we can pass the calling location into the function when using @@ -1559,19 +1549,19 @@ impl<'w> EntityWorldMut<'w> { &mut self, bundle: T, mode: InsertMode, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) -> &mut Self { self.assert_not_despawned(); let change_tick = self.world.change_tick(); let mut bundle_inserter = BundleInserter::new::(self.world, self.location.archetype_id, change_tick); - self.location = - // SAFETY: location matches current entity. `T` matches `bundle_info` - unsafe { - bundle_inserter.insert(self.entity, self.location, bundle, mode, #[cfg(feature = "track_location")] caller) - }; + // SAFETY: location matches current entity. `T` matches `bundle_info` + let (location, after_effect) = + unsafe { bundle_inserter.insert(self.entity, self.location, bundle, mode, caller) }; + self.location = location; self.world.flush(); self.update_location(); + after_effect.apply(self); self } @@ -1595,12 +1585,7 @@ impl<'w> EntityWorldMut<'w> { component_id: ComponentId, component: OwningPtr<'_>, ) -> &mut Self { - self.insert_by_id_with_caller( - component_id, - component, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.insert_by_id_with_caller(component_id, component, MaybeLocation::caller()) } /// # Safety @@ -1610,14 +1595,15 @@ impl<'w> EntityWorldMut<'w> { &mut self, component_id: ComponentId, component: OwningPtr<'_>, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> &mut Self { self.assert_not_despawned(); let change_tick = self.world.change_tick(); - let bundle_id = self - .world - .bundles - .init_component_info(&self.world.components, component_id); + let bundle_id = self.world.bundles.init_component_info( + &mut self.world.storages, + &self.world.components, + component_id, + ); let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); let bundle_inserter = BundleInserter::new_with_id( @@ -1633,7 +1619,6 @@ impl<'w> EntityWorldMut<'w> { self.location, Some(component).into_iter(), Some(storage_type).iter().cloned(), - #[cfg(feature = "track_location")] caller, ); self.world.flush(); @@ -1665,10 +1650,11 @@ impl<'w> EntityWorldMut<'w> { ) -> &mut Self { self.assert_not_despawned(); let change_tick = self.world.change_tick(); - let bundle_id = self - .world - .bundles - .init_dynamic_info(&self.world.components, component_ids); + let bundle_id = self.world.bundles.init_dynamic_info( + &mut self.world.storages, + &self.world.components, + component_ids, + ); let mut storage_types = core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); let bundle_inserter = BundleInserter::new_with_id( @@ -1684,8 +1670,7 @@ impl<'w> EntityWorldMut<'w> { self.location, iter_components, (*storage_types).iter().cloned(), - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); *self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types); self.world.flush(); @@ -1704,7 +1689,7 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[must_use] #[track_caller] - pub fn take(&mut self) -> Option { + pub fn take(&mut self) -> Option { self.assert_not_despawned(); let world = &mut self.world; let storages = &mut world.storages; @@ -1749,8 +1734,7 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, bundle_info, - #[cfg(feature = "track_location")] - Location::caller(), + MaybeLocation::caller(), ); } @@ -1771,6 +1755,7 @@ impl<'w> EntityWorldMut<'w> { // - 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, @@ -1891,11 +1876,7 @@ impl<'w> EntityWorldMut<'w> { /// /// # Safety /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. - unsafe fn remove_bundle( - &mut self, - bundle: BundleId, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> EntityLocation { + unsafe fn remove_bundle(&mut self, bundle: BundleId, caller: MaybeLocation) -> EntityLocation { let entity = self.entity; let world = &mut self.world; let location = self.location; @@ -1938,7 +1919,6 @@ impl<'w> EntityWorldMut<'w> { old_archetype, entity, bundle_info, - #[cfg(feature = "track_location")] caller, ); } @@ -1955,6 +1935,7 @@ impl<'w> EntityWorldMut<'w> { .storages .sparse_sets .get_mut(component_id) + // Set exists because the component existed on the entity .unwrap() .remove(entity); } @@ -1988,30 +1969,18 @@ impl<'w> EntityWorldMut<'w> { // TODO: BundleRemover? #[track_caller] pub fn remove(&mut self) -> &mut Self { - self.remove_with_caller::( - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.remove_with_caller::(MaybeLocation::caller()) } #[inline] - pub(crate) fn remove_with_caller( - &mut self, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> &mut Self { + pub(crate) fn remove_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { self.assert_not_despawned(); let storages = &mut self.world.storages; let components = &mut self.world.components; let bundle_info = self.world.bundles.register_info::(components, storages); // SAFETY: the `BundleInfo` is initialized above - self.location = unsafe { - self.remove_bundle( - bundle_info, - #[cfg(feature = "track_location")] - caller, - ) - }; + self.location = unsafe { self.remove_bundle(bundle_info, caller) }; self.world.flush(); self.update_location(); self @@ -2024,15 +1993,12 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn remove_with_requires(&mut self) -> &mut Self { - self.remove_with_requires_with_caller::( - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.remove_with_requires_with_caller::(MaybeLocation::caller()) } pub(crate) fn remove_with_requires_with_caller( &mut self, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> &mut Self { self.assert_not_despawned(); let storages = &mut self.world.storages; @@ -2042,13 +2008,7 @@ impl<'w> EntityWorldMut<'w> { let bundle_id = bundles.register_contributed_bundle_info::(components, storages); // SAFETY: the dynamic `BundleInfo` is initialized above - self.location = unsafe { - self.remove_bundle( - bundle_id, - #[cfg(feature = "track_location")] - caller, - ) - }; + self.location = unsafe { self.remove_bundle(bundle_id, caller) }; self.world.flush(); self.update_location(); self @@ -2063,17 +2023,11 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn retain(&mut self) -> &mut Self { - self.retain_with_caller::( - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.retain_with_caller::(MaybeLocation::caller()) } #[inline] - pub(crate) fn retain_with_caller( - &mut self, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> &mut Self { + pub(crate) fn retain_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { self.assert_not_despawned(); let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -2090,16 +2044,13 @@ impl<'w> EntityWorldMut<'w> { .components() .filter(|c| !retained_bundle_info.contributed_components().contains(c)) .collect::>(); - let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove); + let remove_bundle = + self.world + .bundles + .init_dynamic_info(&mut self.world.storages, components, to_remove); // SAFETY: the `BundleInfo` for the components to remove is initialized above - self.location = unsafe { - self.remove_bundle( - remove_bundle, - #[cfg(feature = "track_location")] - caller, - ) - }; + self.location = unsafe { self.remove_bundle(remove_bundle, caller) }; self.world.flush(); self.update_location(); self @@ -2115,35 +2066,26 @@ impl<'w> EntityWorldMut<'w> { /// entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { - self.remove_by_id_with_caller( - component_id, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.remove_by_id_with_caller(component_id, MaybeLocation::caller()) } #[inline] pub(crate) fn remove_by_id_with_caller( &mut self, component_id: ComponentId, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> &mut Self { self.assert_not_despawned(); let components = &mut self.world.components; - let bundle_id = self - .world - .bundles - .init_component_info(components, component_id); + let bundle_id = self.world.bundles.init_component_info( + &mut self.world.storages, + components, + component_id, + ); // SAFETY: the `BundleInfo` for this `component_id` is initialized above - self.location = unsafe { - self.remove_bundle( - bundle_id, - #[cfg(feature = "track_location")] - caller, - ) - }; + self.location = unsafe { self.remove_bundle(bundle_id, caller) }; self.world.flush(); self.update_location(); self @@ -2162,19 +2104,14 @@ impl<'w> EntityWorldMut<'w> { self.assert_not_despawned(); let components = &mut self.world.components; - let bundle_id = self - .world - .bundles - .init_dynamic_info(components, component_ids); + let bundle_id = self.world.bundles.init_dynamic_info( + &mut self.world.storages, + components, + component_ids, + ); // SAFETY: the `BundleInfo` for this `bundle_id` is initialized above - unsafe { - self.remove_bundle( - bundle_id, - #[cfg(feature = "track_location")] - Location::caller(), - ) - }; + unsafe { self.remove_bundle(bundle_id, MaybeLocation::caller()) }; self.world.flush(); self.update_location(); @@ -2188,34 +2125,23 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn clear(&mut self) -> &mut Self { - self.clear_with_caller( - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.clear_with_caller(MaybeLocation::caller()) } #[inline] - pub(crate) fn clear_with_caller( - &mut self, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, - ) -> &mut Self { + pub(crate) fn clear_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { self.assert_not_despawned(); let component_ids: Vec = self.archetype().components().collect(); let components = &mut self.world.components; - let bundle_id = self - .world - .bundles - .init_dynamic_info(components, component_ids.as_slice()); + let bundle_id = self.world.bundles.init_dynamic_info( + &mut self.world.storages, + components, + component_ids.as_slice(), + ); // SAFETY: the `BundleInfo` for this `component_id` is initialized above - self.location = unsafe { - self.remove_bundle( - bundle_id, - #[cfg(feature = "track_location")] - caller, - ) - }; + self.location = unsafe { self.remove_bundle(bundle_id, caller) }; self.world.flush(); self.update_location(); self @@ -2235,10 +2161,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] pub fn despawn(self) { - self.despawn_with_caller( - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.despawn_with_caller(MaybeLocation::caller()); } /// Despawns the provided entity and its descendants. @@ -2250,10 +2173,7 @@ impl<'w> EntityWorldMut<'w> { self.despawn(); } - pub(crate) fn despawn_with_caller( - self, - #[cfg(feature = "track_location")] caller: &'static Location, - ) { + pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { self.assert_not_despawned(); let world = self.world; let archetype = &world.archetypes[self.location.archetype_id]; @@ -2272,7 +2192,6 @@ impl<'w> EntityWorldMut<'w> { ON_DESPAWN, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2280,7 +2199,6 @@ impl<'w> EntityWorldMut<'w> { archetype, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); if archetype.has_replace_observer() { @@ -2288,7 +2206,6 @@ impl<'w> EntityWorldMut<'w> { ON_REPLACE, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2296,7 +2213,6 @@ impl<'w> EntityWorldMut<'w> { archetype, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); if archetype.has_remove_observer() { @@ -2304,7 +2220,6 @@ impl<'w> EntityWorldMut<'w> { ON_REMOVE, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2312,7 +2227,6 @@ impl<'w> EntityWorldMut<'w> { archetype, self.entity, archetype.components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2354,6 +2268,7 @@ impl<'w> EntityWorldMut<'w> { table_row = remove_result.table_row; for component_id in archetype.sparse_set_components() { + // set must have existed for the component to be added. let sparse_set = world.storages.sparse_sets.get_mut(component_id).unwrap(); sparse_set.remove(self.entity); } @@ -2383,14 +2298,11 @@ impl<'w> EntityWorldMut<'w> { } world.flush(); - #[cfg(feature = "track_location")] - { - // SAFETY: No structural changes - unsafe { - world - .entities_mut() - .set_spawned_or_despawned_by(self.entity.index(), caller); - } + // SAFETY: No structural changes + unsafe { + world + .entities_mut() + .set_spawned_or_despawned_by(self.entity.index(), caller); } } @@ -2556,36 +2468,29 @@ impl<'w> EntityWorldMut<'w> { &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { - self.observe_with_caller( - observer, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.observe_with_caller(observer, MaybeLocation::caller()) } pub(crate) fn observe_with_caller( &mut self, observer: impl IntoObserverSystem, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> &mut Self { self.assert_not_despawned(); - self.world.spawn_with_caller( - Observer::new(observer).with_entity(self.entity), - #[cfg(feature = "track_location")] - caller, - ); + self.world + .spawn_with_caller(Observer::new(observer).with_entity(self.entity), caller); self.world.flush(); self.update_location(); self } /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityCloneBuilder`]. + /// configured through [`EntityClonerBuilder`]. /// /// By default, the other entity will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// # #[derive(Component, Clone, PartialEq, Debug)] @@ -2602,10 +2507,7 @@ impl<'w> EntityWorldMut<'w> { /// # assert_eq!(world.get::(target), None); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// @@ -2614,11 +2516,11 @@ impl<'w> EntityWorldMut<'w> { pub fn clone_with( &mut self, target: Entity, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); + let mut builder = EntityCloner::build(self.world); config(&mut builder); builder.clone_entity(self.entity, target); @@ -2643,12 +2545,12 @@ impl<'w> EntityWorldMut<'w> { } /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityCloneBuilder`], returning the [`Entity`] of the clone. + /// using [`EntityClonerBuilder`], returning the [`Entity`] of the clone. /// /// By default, the clone will receive all the components of the original that implement /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). /// - /// Configure through [`EntityCloneBuilder`] as follows: + /// Configure through [`EntityClonerBuilder`] as follows: /// ``` /// # use bevy_ecs::prelude::*; /// # #[derive(Component, Clone, PartialEq, Debug)] @@ -2664,24 +2566,21 @@ impl<'w> EntityWorldMut<'w> { /// # assert_eq!(world.get::(entity_clone), None); /// ``` /// - /// See the following for more options: - /// - [`EntityCloneBuilder`] - /// - [`CloneEntityWithObserversExt`](crate::observer::CloneEntityWithObserversExt) - /// - `CloneEntityHierarchyExt` + /// See [`EntityClonerBuilder`] for more options. /// /// # Panics /// /// If this entity has been despawned while this `EntityWorldMut` is still alive. pub fn clone_and_spawn_with( &mut self, - config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, + config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, ) -> Entity { self.assert_not_despawned(); let entity_clone = self.world.entities.reserve_entity(); self.world.flush(); - let mut builder = EntityCloneBuilder::new(self.world); + let mut builder = EntityCloner::build(self.world); config(&mut builder); builder.clone_entity(self.entity, entity_clone); @@ -2702,9 +2601,10 @@ impl<'w> EntityWorldMut<'w> { pub fn clone_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); - builder.deny_all().allow::(); - builder.clone_entity(self.entity, target); + EntityCloner::build(self.world) + .deny_all() + .allow::() + .clone_entity(self.entity, target); self.world.flush(); self.update_location(); @@ -2724,10 +2624,11 @@ impl<'w> EntityWorldMut<'w> { pub fn move_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); - let mut builder = EntityCloneBuilder::new(self.world); - builder.deny_all().allow::(); - builder.move_components(true); - builder.clone_entity(self.entity, target); + EntityCloner::build(self.world) + .deny_all() + .allow::() + .move_components(true) + .clone_entity(self.entity, target); self.world.flush(); self.update_location(); @@ -2735,12 +2636,11 @@ impl<'w> EntityWorldMut<'w> { } /// Returns the source code location from which this entity has last been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.world() .entities() .entity_get_spawned_or_despawned_by(self.entity) - .unwrap() + .map(|location| location.unwrap()) } } @@ -2751,14 +2651,13 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( archetype: &Archetype, entity: Entity, bundle_info: &BundleInfo, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) { if archetype.has_replace_observer() { deferred_world.trigger_observers( ON_REPLACE, entity, bundle_info.iter_explicit_components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2766,7 +2665,6 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( archetype, entity, bundle_info.iter_explicit_components(), - #[cfg(feature = "track_location")] caller, ); if archetype.has_remove_observer() { @@ -2774,7 +2672,6 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( ON_REMOVE, entity, bundle_info.iter_explicit_components(), - #[cfg(feature = "track_location")] caller, ); } @@ -2782,7 +2679,6 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( archetype, entity, bundle_info.iter_explicit_components(), - #[cfg(feature = "track_location")] caller, ); } @@ -3296,8 +3192,7 @@ impl<'w> FilteredEntityRef<'w> { } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } } @@ -3392,6 +3287,24 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> { } } +impl<'a, B: Bundle> From<&'a EntityRefExcept<'_, B>> for FilteredEntityRef<'a> { + fn from(value: &'a EntityRefExcept<'_, B>) -> Self { + // SAFETY: + // - The FilteredEntityRef has the same component access as the given EntityRefExcept. + unsafe { + let mut access = Access::default(); + access.read_all(); + let components = value.entity.world().components(); + B::get_component_ids(components, &mut |maybe_id| { + if let Some(id) = maybe_id { + access.remove_component_read(id); + } + }); + FilteredEntityRef::new(value.entity, access) + } + } +} + impl PartialEq for FilteredEntityRef<'_> { fn eq(&self, other: &Self) -> bool { self.entity() == other.entity() @@ -3660,8 +3573,7 @@ impl<'w> FilteredEntityMut<'w> { } /// Returns the source code location from which this entity has last been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } } @@ -3718,6 +3630,24 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a> { } } +impl<'a, B: Bundle> From<&'a EntityMutExcept<'_, B>> for FilteredEntityMut<'a> { + fn from(value: &'a EntityMutExcept<'_, B>) -> Self { + // SAFETY: + // - The FilteredEntityMut has the same component access as the given EntityMutExcept. + unsafe { + let mut access = Access::default(); + access.write_all(); + let components = value.entity.world().components(); + B::get_component_ids(components, &mut |maybe_id| { + if let Some(id) = maybe_id { + access.remove_component_read(id); + } + }); + FilteredEntityMut::new(value.entity, access) + } + } +} + impl PartialEq for FilteredEntityMut<'_> { fn eq(&self, other: &Self) -> bool { self.entity() == other.entity() @@ -3840,10 +3770,96 @@ where } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } + + /// Gets the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API [`Self::get`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityRefExcept::get`], this returns a raw pointer to the component, + /// which is only valid while the [`EntityRefExcept`] is alive. + #[inline] + pub fn get_by_id(&self, component_id: ComponentId) -> Option> { + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have read access for this component + unsafe { self.entity.get_by_id(component_id) } + }) + .flatten() + } + + /// Returns `true` if the current entity has a component of type `T`. + /// Otherwise, this returns `false`. + /// + /// ## Notes + /// + /// If you do not know the concrete type of a component, consider using + /// [`Self::contains_id`] or [`Self::contains_type_id`]. + #[inline] + pub fn contains(&self) -> bool { + self.contains_type_id(TypeId::of::()) + } + + /// Returns `true` if the current entity has a component identified by `component_id`. + /// Otherwise, this returns false. + /// + /// ## Notes + /// + /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. + /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using + /// [`Self::contains_type_id`]. + #[inline] + pub fn contains_id(&self, component_id: ComponentId) -> bool { + self.entity.contains_id(component_id) + } + + /// Returns `true` if the current entity has a component with the type identified by `type_id`. + /// Otherwise, this returns false. + /// + /// ## Notes + /// + /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. + /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. + #[inline] + pub fn contains_type_id(&self, type_id: TypeId) -> bool { + self.entity.contains_type_id(type_id) + } + + /// Retrieves the change ticks for the given component. This can be useful for implementing change + /// detection in custom runtimes. + #[inline] + pub fn get_change_ticks(&self) -> Option { + let component_id = self.entity.world().components().get_id(TypeId::of::())?; + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have read access + unsafe { self.entity.get_change_ticks::() } + }) + .flatten() + } + + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change + /// detection in custom runtimes. + /// + /// **You should prefer to use the typed API [`Self::get_change_ticks`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + #[inline] + pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have read access + unsafe { self.entity.get_change_ticks_by_id(component_id) } + }) + .flatten() + } } impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B> @@ -3998,10 +4014,81 @@ where } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(&self) -> &'static Location<'static> { + pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() } + + /// Returns `true` if the current entity has a component of type `T`. + /// Otherwise, this returns `false`. + /// + /// ## Notes + /// + /// If you do not know the concrete type of a component, consider using + /// [`Self::contains_id`] or [`Self::contains_type_id`]. + #[inline] + pub fn contains(&self) -> bool { + self.contains_type_id(TypeId::of::()) + } + + /// Returns `true` if the current entity has a component identified by `component_id`. + /// Otherwise, this returns false. + /// + /// ## Notes + /// + /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. + /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using + /// [`Self::contains_type_id`]. + #[inline] + pub fn contains_id(&self, component_id: ComponentId) -> bool { + self.entity.contains_id(component_id) + } + + /// Returns `true` if the current entity has a component with the type identified by `type_id`. + /// Otherwise, this returns false. + /// + /// ## Notes + /// + /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. + /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. + #[inline] + pub fn contains_type_id(&self, type_id: TypeId) -> bool { + self.entity.contains_type_id(type_id) + } + + /// Gets the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API [`Self::get`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityMutExcept::get`], this returns a raw pointer to the component, + /// which is only valid while the [`EntityMutExcept`] is alive. + #[inline] + pub fn get_by_id(&'w self, component_id: ComponentId) -> Option> { + self.as_readonly().get_by_id(component_id) + } + + /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API [`Self::get_mut`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityMutExcept::get_mut`], this returns a raw pointer to the component, + /// which is only valid while the [`EntityMutExcept`] is alive. + #[inline] + pub fn get_mut_by_id( + &mut self, + component_id: ComponentId, + ) -> Option> { + let components = self.entity.world().components(); + (!bundle_contains_component::(components, component_id)) + .then(|| { + // SAFETY: We have write access + unsafe { self.entity.get_mut_by_id(component_id).ok() } + }) + .flatten() + } } impl PartialEq for EntityMutExcept<'_, B> { @@ -4071,7 +4158,7 @@ unsafe fn insert_dynamic_bundle< location: EntityLocation, components: I, storage_types: S, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> EntityLocation { struct DynamicInsertBundle<'a, I: Iterator)>> { components: I, @@ -4080,6 +4167,7 @@ unsafe fn insert_dynamic_bundle< impl<'a, I: Iterator)>> DynamicBundle for DynamicInsertBundle<'a, I> { + type Effect = (); fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { self.components.for_each(|(t, ptr)| func(t, ptr)); } @@ -4091,14 +4179,9 @@ unsafe fn insert_dynamic_bundle< // SAFETY: location matches current entity. unsafe { - bundle_inserter.insert( - entity, - location, - bundle, - InsertMode::Replace, - #[cfg(feature = "track_location")] - caller, - ) + bundle_inserter + .insert(entity, location, bundle, InsertMode::Replace, caller) + .0 } } @@ -4113,6 +4196,9 @@ unsafe fn insert_dynamic_bundle< /// - `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, @@ -4400,14 +4486,11 @@ mod tests { use alloc::{vec, vec::Vec}; use bevy_ptr::{OwningPtr, Ptr}; use core::panic::AssertUnwindSafe; - - #[cfg(feature = "track_location")] - use {core::panic::Location, std::sync::OnceLock}; + use std::sync::OnceLock; use crate::component::HookContext; use crate::{ - self as bevy_ecs, - change_detection::MutUntyped, + change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, prelude::*, system::{assert_is_system, RunSystemOnce as _}, @@ -5665,10 +5748,11 @@ mod tests { let entity_b = world.spawn_empty().id(); world.entity_mut(entity_a).clone_with(entity_b, |builder| { - builder.move_components(true); - builder.without_required_components(|builder| { - builder.deny::(); - }); + builder + .move_components(true) + .without_required_components(|builder| { + builder.deny::(); + }); }); assert_eq!(world.entity(entity_a).get::(), Some(&A)); @@ -5685,7 +5769,6 @@ mod tests { } #[test] - #[cfg(feature = "track_location")] fn update_despawned_by_after_observers() { let mut world = World::new(); @@ -5693,19 +5776,19 @@ mod tests { #[component(on_remove = get_tracked)] struct C; - static TRACKED: OnceLock<&'static Location<'static>> = OnceLock::new(); + static TRACKED: OnceLock = OnceLock::new(); fn get_tracked(world: DeferredWorld, HookContext { entity, .. }: HookContext) { TRACKED.get_or_init(|| { world .entities .entity_get_spawned_or_despawned_by(entity) - .unwrap() + .map(|l| l.unwrap()) }); } #[track_caller] - fn caller_spawn(world: &mut World) -> (Entity, &'static Location<'static>) { - let caller = Location::caller(); + fn caller_spawn(world: &mut World) -> (Entity, MaybeLocation) { + let caller = MaybeLocation::caller(); (world.spawn(C).id(), caller) } let (entity, spawner) = caller_spawn(&mut world); @@ -5715,13 +5798,13 @@ mod tests { world .entities() .entity_get_spawned_or_despawned_by(entity) - .unwrap() + .map(|l| l.unwrap()) ); #[track_caller] - fn caller_despawn(world: &mut World, entity: Entity) -> &'static Location<'static> { + fn caller_despawn(world: &mut World, entity: Entity) -> MaybeLocation { world.despawn(entity); - Location::caller() + MaybeLocation::caller() } let despawner = caller_despawn(&mut world, entity); @@ -5731,7 +5814,7 @@ mod tests { world .entities() .entity_get_spawned_or_despawned_by(entity) - .unwrap() + .map(|l| l.unwrap()) ); } diff --git a/crates/bevy_ecs/src/world/error.rs b/crates/bevy_ecs/src/world/error.rs index 7609b15e019ae..1542a128512d6 100644 --- a/crates/bevy_ecs/src/world/error.rs +++ b/crates/bevy_ecs/src/world/error.rs @@ -1,39 +1,26 @@ //! Contains error types returned by bevy's schedule. use alloc::vec::Vec; -use thiserror::Error; use crate::{ component::ComponentId, - entity::{Entity, EntityDoesNotExistDetails}, + entity::{Entity, EntityDoesNotExistError}, schedule::InternedScheduleLabel, }; /// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist. /// /// [`World::try_run_schedule`]: crate::world::World::try_run_schedule -#[derive(Error, Debug)] +#[derive(thiserror::Error, Debug)] #[error("The schedule with the label {0:?} was not found.")] pub struct TryRunScheduleError(pub InternedScheduleLabel); -/// The error type returned by [`World::try_despawn`] if the provided entity does not exist. -/// -/// [`World::try_despawn`]: crate::world::World::try_despawn -#[derive(Error, Debug, Clone, Copy)] -#[error("Could not despawn the entity with ID {entity} because it {details}")] -pub struct TryDespawnError { - /// The entity's ID. - pub entity: Entity, - /// Details on why the entity does not exist, if available. - pub details: EntityDoesNotExistDetails, -} - /// The error type returned by [`World::try_insert_batch`] and [`World::try_insert_batch_if_new`] /// if any of the provided entities do not exist. /// /// [`World::try_insert_batch`]: crate::world::World::try_insert_batch /// [`World::try_insert_batch_if_new`]: crate::world::World::try_insert_batch_if_new -#[derive(Error, Debug, Clone)] +#[derive(thiserror::Error, Debug, Clone)] #[error("Could not insert bundles of type {bundle_type} into the entities with the following IDs because they do not exist: {entities:?}")] pub struct TryInsertBatchError { /// The bundles' type name. @@ -42,8 +29,13 @@ pub struct TryInsertBatchError { pub entities: Vec, } +/// An error that occurs when a specified [`Entity`] could not be despawned. +#[derive(thiserror::Error, Debug, Clone, Copy)] +#[error("Could not despawn entity: {0}")] +pub struct EntityDespawnError(#[from] pub EntityMutableFetchError); + /// An error that occurs when dynamically retrieving components from an entity. -#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] pub enum EntityComponentError { /// The component with the given [`ComponentId`] does not exist on the entity. #[error("The component with ID {0:?} does not exist on the entity.")] @@ -54,24 +46,12 @@ pub enum EntityComponentError { } /// An error that occurs when fetching entities mutably from a world. -#[derive(Error, Debug, Clone, Copy)] -pub enum EntityFetchError { +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)] +pub enum EntityMutableFetchError { /// The entity with the given ID does not exist. - #[error("The entity with ID {0} {1}")] - NoSuchEntity(Entity, EntityDoesNotExistDetails), + #[error(transparent)] + EntityDoesNotExist(#[from] EntityDoesNotExistError), /// The entity with the given ID was requested mutably more than once. #[error("The entity with ID {0} was requested mutably more than once")] AliasedMutability(Entity), } - -impl PartialEq for EntityFetchError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true, - (Self::AliasedMutability(e1), Self::AliasedMutability(e2)) if e1 == e2 => true, - _ => false, - } - } -} - -impl Eq for EntityFetchError {} diff --git a/crates/bevy_ecs/src/world/filtered_resource.rs b/crates/bevy_ecs/src/world/filtered_resource.rs index 78794866e84b5..e5197e05703b6 100644 --- a/crates/bevy_ecs/src/world/filtered_resource.rs +++ b/crates/bevy_ecs/src/world/filtered_resource.rs @@ -5,9 +5,7 @@ use crate::{ resource::Resource, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; -use bevy_ptr::Ptr; -#[cfg(feature = "track_location")] -use bevy_ptr::UnsafeCellDeref; +use bevy_ptr::{Ptr, UnsafeCellDeref}; /// Provides read-only access to a set of [`Resource`]s defined by the contained [`Access`]. /// @@ -159,17 +157,16 @@ impl<'w, 's> FilteredResources<'w, 's> { return None; } // SAFETY: We have read access to this resource - unsafe { self.world.get_resource_with_ticks(component_id) }.map( - |(value, ticks, _caller)| Ref { + unsafe { self.world.get_resource_with_ticks(component_id) }.map(|(value, ticks, caller)| { + Ref { // SAFETY: `component_id` was obtained from the type ID of `R`. value: unsafe { value.deref() }, // SAFETY: We have read access to the resource, so no mutable reference can exist. ticks: unsafe { Ticks::from_tick_cells(ticks, self.last_run, self.this_run) }, - #[cfg(feature = "track_location")] // SAFETY: We have read access to the resource, so no mutable reference can exist. - changed_by: unsafe { _caller.deref() }, - }, - ) + changed_by: unsafe { caller.map(|caller| caller.deref()) }, + } + }) } /// Gets a pointer to the resource with the given [`ComponentId`] if it exists and the `FilteredResources` has access to it. @@ -477,17 +474,16 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> { return None; } // SAFETY: We have access to this resource in `access`, and the caller ensures that there are no conflicting borrows for the duration of the returned value. - unsafe { self.world.get_resource_with_ticks(component_id) }.map( - |(value, ticks, _caller)| MutUntyped { + unsafe { self.world.get_resource_with_ticks(component_id) }.map(|(value, ticks, caller)| { + MutUntyped { // SAFETY: We have exclusive access to the underlying storage. value: unsafe { value.assert_unique() }, // SAFETY: We have exclusive access to the underlying storage. ticks: unsafe { TicksMut::from_tick_cells(ticks, self.last_run, self.this_run) }, - #[cfg(feature = "track_location")] // SAFETY: We have exclusive access to the underlying storage. - changed_by: unsafe { _caller.deref_mut() }, - }, - ) + changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, + } + }) } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8ea17168a38c4..db812ff1652a7 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -32,42 +32,41 @@ pub use spawn_batch::*; use crate::{ archetype::{ArchetypeId, ArchetypeRow, Archetypes}, - bundle::{Bundle, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode}, - change_detection::{MutUntyped, TicksMut}, + bundle::{ + Bundle, BundleEffect, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode, + NoBundleEffect, + }, + change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ - Component, ComponentCloneHandlers, ComponentDescriptor, ComponentHooks, ComponentId, - ComponentInfo, ComponentTicks, Components, Mutable, RequiredComponents, - RequiredComponentsError, Tick, + Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks, + Components, Mutable, RequiredComponents, RequiredComponentsError, Tick, + }, + entity::{ + AllocAtWithoutReplacement, Entities, Entity, EntityDoesNotExistError, EntityLocation, }, - entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, - entity_disabling::{DefaultQueryFilters, Disabled}, + entity_disabling::DefaultQueryFilters, event::{Event, EventId, Events, SendBatchIds}, observer::Observers, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, removal_detection::RemovedComponentEvents, resource::Resource, - result::Result, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, system::Commands, world::{ command_queue::RawCommandQueue, - error::{EntityFetchError, TryDespawnError, TryInsertBatchError, TryRunScheduleError}, + error::{ + EntityDespawnError, EntityMutableFetchError, TryInsertBatchError, TryRunScheduleError, + }, }, }; use alloc::{boxed::Box, vec::Vec}; use bevy_platform_support::sync::atomic::{AtomicU32, Ordering}; -use bevy_ptr::{OwningPtr, Ptr}; +use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use core::{any::TypeId, fmt}; use log::warn; use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; -#[cfg(feature = "track_location")] -use bevy_ptr::UnsafeCellDeref; - -#[cfg(feature = "track_location")] -use core::panic::Location; - /// Stores and exposes operations on [entities](Entity), [components](Component), resources, /// and their associated metadata. /// @@ -160,10 +159,8 @@ impl World { let on_despawn = OnDespawn::register_component_id(self); assert_eq!(ON_DESPAWN, on_despawn); - let disabled = self.register_component::(); - let mut filters = DefaultQueryFilters::default(); - filters.set_disabled(disabled); - self.insert_resource(filters); + // This sets up `Disabled` as a disabling component, via the FromWorld impl + self.init_resource::(); } /// Creates a new empty [`World`]. /// @@ -251,7 +248,15 @@ impl World { /// Registers a new [`Component`] type and returns the [`ComponentId`] created for it. pub fn register_component(&mut self) -> ComponentId { - self.components.register_component::(&mut self.storages) + self.components.register_component::() + } + + /// Registers a component type as "disabling", + /// using [default query filters](DefaultQueryFilters) to exclude entities with the component from queries. + pub fn register_disabling_component(&mut self) { + let component_id = self.register_component::(); + let mut dqf = self.resource_mut::(); + dqf.register_disabling_component(component_id); } /// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type. @@ -528,7 +533,7 @@ impl World { descriptor: ComponentDescriptor, ) -> ComponentId { self.components - .register_component_with_descriptor(&mut self.storages, descriptor) + .register_component_with_descriptor(descriptor) } /// Returns the [`ComponentId`] of the given [`Component`] type `T`. @@ -693,7 +698,7 @@ impl World { match self.get_entity(entities) { Ok(fetched) => fetched, - Err(entity) => panic_no_entity(self, entity), + Err(error) => panic_no_entity(self, error.entity), } } @@ -820,7 +825,7 @@ impl World { #[inline(never)] #[cold] #[track_caller] - fn panic_on_err(e: EntityFetchError) -> ! { + fn panic_on_err(e: EntityMutableFetchError) -> ! { panic!("{e}"); } @@ -832,25 +837,23 @@ impl World { /// Returns the components of an [`Entity`] through [`ComponentInfo`]. #[inline] - pub fn inspect_entity(&self, entity: Entity) -> impl Iterator { + pub fn inspect_entity( + &self, + entity: Entity, + ) -> Result, EntityDoesNotExistError> { let entity_location = self .entities() .get(entity) - .unwrap_or_else(|| panic!("Entity {entity} does not exist")); + .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; let archetype = self .archetypes() .get(entity_location.archetype_id) - .unwrap_or_else(|| { - panic!( - "Archetype {:?} does not exist", - entity_location.archetype_id - ) - }); + .expect("ArchetypeId was retrieved from an EntityLocation and should correspond to an Archetype"); - archetype + Ok(archetype .components() - .filter_map(|id| self.components().get_info(id)) + .filter_map(|id| self.components().get_info(id))) } /// Returns [`EntityRef`]s that expose read-only operations for the given @@ -868,7 +871,7 @@ impl World { /// # Errors /// /// If any of the given `entities` do not exist in the world, the first - /// [`Entity`] found to be missing will be returned in the [`Err`]. + /// [`Entity`] found to be missing will return an [`EntityDoesNotExistError`]. /// /// # Examples /// @@ -876,7 +879,10 @@ impl World { /// /// [`EntityHashSet`]: crate::entity::hash_set::EntityHashSet #[inline] - pub fn get_entity(&self, entities: F) -> Result, Entity> { + pub fn get_entity( + &self, + entities: F, + ) -> Result, EntityDoesNotExistError> { let cell = self.as_unsafe_world_cell_readonly(); // SAFETY: `&self` gives read access to the entire world, and prevents mutable access. unsafe { entities.fetch_ref(cell) } @@ -904,9 +910,9 @@ impl World { /// /// # Errors /// - /// - Returns [`EntityFetchError::NoSuchEntity`] if any of the given `entities` do not exist in the world. + /// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world. /// - Only the first entity found to be missing will be returned. - /// - Returns [`EntityFetchError::AliasedMutability`] if the same entity is requested multiple times. + /// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times. /// /// # Examples /// @@ -917,7 +923,7 @@ impl World { pub fn get_entity_mut( &mut self, entities: F, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { let cell = self.as_unsafe_world_cell(); // SAFETY: `&mut self` gives mutable access to the entire world, // and prevents any other access to the world. @@ -1011,13 +1017,7 @@ impl World { self.flush(); let entity = self.entities.alloc(); // SAFETY: entity was just allocated - unsafe { - self.spawn_at_empty_internal( - entity, - #[cfg(feature = "track_location")] - Location::caller(), - ) - } + unsafe { self.spawn_at_empty_internal(entity, MaybeLocation::caller()) } } /// Spawns a new [`Entity`] with a given [`Bundle`] of [components](`Component`) and returns @@ -1082,31 +1082,21 @@ impl World { /// ``` #[track_caller] pub fn spawn(&mut self, bundle: B) -> EntityWorldMut { - self.spawn_with_caller( - bundle, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.spawn_with_caller(bundle, MaybeLocation::caller()) } pub(crate) fn spawn_with_caller( &mut self, bundle: B, - #[cfg(feature = "track_location")] caller: &'static Location<'static>, + caller: MaybeLocation, ) -> EntityWorldMut { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); let mut bundle_spawner = BundleSpawner::new::(self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent - let mut entity_location = unsafe { - bundle_spawner.spawn_non_existent( - entity, - bundle, - #[cfg(feature = "track_location")] - caller, - ) - }; + let (mut entity_location, after_effect) = + unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; // SAFETY: command_queue is not referenced anywhere else if !unsafe { self.command_queue.is_empty() } { @@ -1117,12 +1107,13 @@ impl World { .unwrap_or(EntityLocation::INVALID); } - #[cfg(feature = "track_location")] self.entities .set_spawned_or_despawned_by(entity.index(), caller); // SAFETY: entity and location are valid, as they were just created above - unsafe { EntityWorldMut::new(self, entity, entity_location) } + let mut entity = unsafe { EntityWorldMut::new(self, entity, entity_location) }; + after_effect.apply(&mut entity); + entity } /// # Safety @@ -1130,7 +1121,7 @@ impl World { unsafe fn spawn_at_empty_internal( &mut self, entity: Entity, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) -> EntityWorldMut { let archetype = self.archetypes.empty_mut(); // PERF: consider avoiding allocating entities in the empty archetype unless needed @@ -1140,7 +1131,6 @@ impl World { let location = unsafe { archetype.allocate(entity, table_row) }; self.entities.set(entity.index(), location); - #[cfg(feature = "track_location")] self.entities .set_spawned_or_despawned_by(entity.index(), caller); @@ -1173,14 +1163,9 @@ impl World { pub fn spawn_batch(&mut self, iter: I) -> SpawnBatchIter<'_, I::IntoIter> where I: IntoIterator, - I::Item: Bundle, + I::Item: Bundle, { - SpawnBatchIter::new( - self, - iter.into_iter(), - #[cfg(feature = "track_location")] - Location::caller(), - ) + SpawnBatchIter::new(self, iter.into_iter(), MaybeLocation::caller()) } /// Retrieves a reference to the given `entity`'s [`Component`] of the given type. @@ -1264,21 +1249,10 @@ impl World { &mut self, entity: Entity, f: impl FnOnce(&mut T) -> R, - ) -> Result, EntityFetchError> { + ) -> Result, EntityMutableFetchError> { let mut world = DeferredWorld::from(&mut *self); - let result = match world.modify_component(entity, f) { - Ok(result) => result, - Err(EntityFetchError::AliasedMutability(..)) => { - return Err(EntityFetchError::AliasedMutability(entity)) - } - Err(EntityFetchError::NoSuchEntity(..)) => { - return Err(EntityFetchError::NoSuchEntity( - entity, - self.entities().entity_does_not_exist_error_details(entity), - )) - } - }; + let result = world.modify_component(entity, f)?; self.flush(); Ok(result) @@ -1313,11 +1287,7 @@ impl World { #[track_caller] #[inline] pub fn despawn(&mut self, entity: Entity) -> bool { - if let Err(error) = self.despawn_with_caller( - entity, - #[cfg(feature = "track_location")] - Location::caller(), - ) { + if let Err(error) = self.despawn_with_caller(entity, MaybeLocation::caller()) { warn!("{error}"); false } else { @@ -1328,7 +1298,7 @@ impl World { /// Despawns the given `entity`, if it exists. This will also remove all of the entity's /// [`Components`](Component). /// - /// Returns a [`TryDespawnError`] if the entity does not exist. + /// Returns an [`EntityDespawnError`] if the entity does not exist. /// /// # Note /// @@ -1336,33 +1306,20 @@ impl World { /// to despawn descendants. For example, this will recursively despawn [`Children`](crate::hierarchy::Children). #[track_caller] #[inline] - pub fn try_despawn(&mut self, entity: Entity) -> Result<(), TryDespawnError> { - self.despawn_with_caller( - entity, - #[cfg(feature = "track_location")] - Location::caller(), - ) + pub fn try_despawn(&mut self, entity: Entity) -> Result<(), EntityDespawnError> { + self.despawn_with_caller(entity, MaybeLocation::caller()) } #[inline] pub(crate) fn despawn_with_caller( &mut self, entity: Entity, - #[cfg(feature = "track_location")] caller: &'static Location, - ) -> Result<(), TryDespawnError> { + caller: MaybeLocation, + ) -> Result<(), EntityDespawnError> { self.flush(); - if let Ok(entity) = self.get_entity_mut(entity) { - entity.despawn_with_caller( - #[cfg(feature = "track_location")] - caller, - ); - Ok(()) - } else { - Err(TryDespawnError { - entity, - details: self.entities().entity_does_not_exist_error_details(entity), - }) - } + let entity = self.get_entity_mut(entity)?; + entity.despawn_with_caller(caller); + Ok(()) } /// Clears the internal component tracker state. @@ -1626,8 +1583,7 @@ impl World { #[inline] #[track_caller] pub fn init_resource(&mut self) -> ComponentId { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let component_id = self.components.register_resource::(); if self .storages @@ -1639,12 +1595,7 @@ impl World { OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - self.insert_resource_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + self.insert_resource_by_id(component_id, ptr, caller); } }); } @@ -1659,11 +1610,7 @@ impl World { #[inline] #[track_caller] pub fn insert_resource(&mut self, value: R) { - self.insert_resource_with_caller( - value, - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.insert_resource_with_caller(value, MaybeLocation::caller()); } /// Split into a new function so we can pass the calling location into the function when using @@ -1672,18 +1619,13 @@ impl World { pub(crate) fn insert_resource_with_caller( &mut self, value: R, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) { let component_id = self.components.register_resource::(); OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - self.insert_resource_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + self.insert_resource_by_id(component_id, ptr, caller); } }); } @@ -1702,8 +1644,7 @@ impl World { #[inline] #[track_caller] pub fn init_non_send_resource(&mut self) -> ComponentId { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let component_id = self.components.register_non_send::(); if self .storages @@ -1715,12 +1656,7 @@ impl World { OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - self.insert_non_send_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + self.insert_non_send_by_id(component_id, ptr, caller); } }); } @@ -1739,18 +1675,12 @@ impl World { #[inline] #[track_caller] pub fn insert_non_send_resource(&mut self, value: R) { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let component_id = self.components.register_non_send::(); OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - self.insert_non_send_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + self.insert_non_send_by_id(component_id, ptr, caller); } }); } @@ -2025,8 +1955,7 @@ impl World { &mut self, func: impl FnOnce() -> R, ) -> Mut<'_, R> { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let change_tick = self.change_tick(); let last_change_tick = self.last_change_tick(); @@ -2036,12 +1965,7 @@ impl World { OwningPtr::make(func(), |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - data.insert( - ptr, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + data.insert(ptr, change_tick, caller); } }); } @@ -2089,8 +2013,7 @@ impl World { /// ``` #[track_caller] pub fn get_resource_or_init(&mut self) -> Mut<'_, R> { - #[cfg(feature = "track_location")] - let caller = Location::caller(); + let caller = MaybeLocation::caller(); let change_tick = self.change_tick(); let last_change_tick = self.last_change_tick(); @@ -2105,12 +2028,7 @@ impl World { OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { - self.insert_resource_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - caller, - ); + self.insert_resource_by_id(component_id, ptr, caller); } }); } @@ -2235,13 +2153,9 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { - self.insert_or_spawn_batch_with_caller( - iter, - #[cfg(feature = "track_location")] - Location::caller(), - ) + 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 @@ -2250,12 +2164,12 @@ impl World { pub(crate) fn insert_or_spawn_batch_with_caller( &mut self, iter: I, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) -> Result<(), Vec> where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { self.flush(); @@ -2300,7 +2214,6 @@ impl World { location, bundle, InsertMode::Replace, - #[cfg(feature = "track_location")] caller, ) }; @@ -2322,7 +2235,6 @@ impl World { location, bundle, InsertMode::Replace, - #[cfg(feature = "track_location")] caller, ) }; @@ -2334,27 +2246,13 @@ impl World { 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, - #[cfg(feature = "track_location")] - caller, - ) - }; + 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, - #[cfg(feature = "track_location")] - caller, - ) - }; + unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); } } @@ -2391,14 +2289,9 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { - self.insert_batch_with_caller( - batch, - InsertMode::Replace, - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.insert_batch_with_caller(batch, InsertMode::Replace, MaybeLocation::caller()); } /// For a given batch of ([`Entity`], [`Bundle`]) pairs, @@ -2421,14 +2314,9 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { - self.insert_batch_with_caller( - batch, - InsertMode::Keep, - #[cfg(feature = "track_location")] - Location::caller(), - ); + self.insert_batch_with_caller(batch, InsertMode::Keep, MaybeLocation::caller()); } /// Split into a new function so we can differentiate the calling location. @@ -2441,11 +2329,11 @@ impl World { &mut self, batch: I, insert_mode: InsertMode, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { struct InserterArchetypeCache<'w> { inserter: BundleInserter<'w>, @@ -2481,7 +2369,6 @@ impl World { first_location, first_bundle, insert_mode, - #[cfg(feature = "track_location")] caller, ) }; @@ -2504,14 +2391,9 @@ impl World { } // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { - cache.inserter.insert( - entity, - location, - bundle, - insert_mode, - #[cfg(feature = "track_location")] - caller, - ) + cache + .inserter + .insert(entity, location, bundle, insert_mode, caller) }; } else { panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details(entity)); @@ -2541,14 +2423,9 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { - self.try_insert_batch_with_caller( - batch, - InsertMode::Replace, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.try_insert_batch_with_caller(batch, InsertMode::Replace, MaybeLocation::caller()) } /// For a given batch of ([`Entity`], [`Bundle`]) pairs, /// adds the `Bundle` of components to each `Entity` without overwriting. @@ -2568,14 +2445,9 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { - self.try_insert_batch_with_caller( - batch, - InsertMode::Keep, - #[cfg(feature = "track_location")] - Location::caller(), - ) + self.try_insert_batch_with_caller(batch, InsertMode::Keep, MaybeLocation::caller()) } /// Split into a new function so we can differentiate the calling location. @@ -2592,12 +2464,12 @@ impl World { &mut self, batch: I, insert_mode: InsertMode, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) -> Result<(), TryInsertBatchError> where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle, { struct InserterArchetypeCache<'w> { inserter: BundleInserter<'w>, @@ -2638,7 +2510,6 @@ impl World { first_location, first_bundle, insert_mode, - #[cfg(feature = "track_location")] caller, ) }; @@ -2670,14 +2541,9 @@ impl World { } // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { - cache.inserter.insert( - entity, - location, - bundle, - insert_mode, - #[cfg(feature = "track_location")] - caller, - ) + cache + .inserter + .insert(entity, location, bundle, insert_mode, caller) }; } else { invalid_entities.push(entity); @@ -2741,7 +2607,7 @@ impl World { let change_tick = self.change_tick(); let component_id = self.components.get_resource_id(TypeId::of::())?; - let (ptr, mut ticks, mut _caller) = self + let (ptr, mut ticks, mut caller) = self .storages .resources .get_mut(component_id) @@ -2757,8 +2623,7 @@ impl World { last_run: last_change_tick, this_run: change_tick, }, - #[cfg(feature = "track_location")] - changed_by: &mut _caller, + changed_by: caller.as_mut(), }; let result = f(self, value_mut); assert!(!self.contains_resource::(), @@ -2770,12 +2635,7 @@ impl World { // SAFETY: pointer is of type R unsafe { self.storages.resources.get_mut(component_id).map(|info| { - info.insert_with_ticks( - ptr, - ticks, - #[cfg(feature = "track_location")] - _caller, - ); + info.insert_with_ticks(ptr, ticks, caller); }) } })?; @@ -2830,19 +2690,14 @@ impl World { &mut self, component_id: ComponentId, value: OwningPtr<'_>, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) { let change_tick = self.change_tick(); let resource = self.initialize_resource_internal(component_id); // SAFETY: `value` is valid for `component_id`, ensured by caller unsafe { - resource.insert( - value, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + resource.insert(value, change_tick, caller); } } @@ -2864,19 +2719,14 @@ impl World { &mut self, component_id: ComponentId, value: OwningPtr<'_>, - #[cfg(feature = "track_location")] caller: &'static Location, + caller: MaybeLocation, ) { let change_tick = self.change_tick(); let resource = self.initialize_non_send_internal(component_id); // SAFETY: `value` is valid for `component_id`, ensured by caller unsafe { - resource.insert( - value, - change_tick, - #[cfg(feature = "track_location")] - caller, - ); + resource.insert(value, change_tick, caller); } } @@ -3190,35 +3040,6 @@ impl World { // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } - - /// Retrieves a mutable reference to the [`ComponentCloneHandlers`]. Can be used to set and update clone functions for components. - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// use bevy_ecs::component::{ComponentId, ComponentCloneHandler}; - /// use bevy_ecs::entity::ComponentCloneCtx; - /// use bevy_ecs::world::DeferredWorld; - /// - /// fn custom_clone_handler( - /// _world: &mut DeferredWorld, - /// _ctx: &mut ComponentCloneCtx, - /// ) { - /// // Custom cloning logic for component - /// } - /// - /// #[derive(Component)] - /// struct ComponentA; - /// - /// let mut world = World::new(); - /// - /// let component_id = world.register_component::(); - /// - /// world.get_component_clone_handlers_mut() - /// .set_component_handler(component_id, ComponentCloneHandler::custom_handler(custom_clone_handler)) - /// ``` - pub fn get_component_clone_handlers_mut(&mut self) -> &mut ComponentCloneHandlers { - self.components.get_component_clone_handlers_mut() - } } impl World { @@ -3435,7 +3256,7 @@ impl World { .get_info(component_id) .debug_checked_unwrap() }; - let (ptr, ticks, _caller) = data.get_with_ticks()?; + let (ptr, ticks, caller) = data.get_with_ticks()?; // SAFETY: // - We have exclusive access to the world, so no other code can be aliasing the `TickCells` @@ -3454,11 +3275,10 @@ impl World { // - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one value: unsafe { ptr.assert_unique() }, ticks, - #[cfg(feature = "track_location")] // SAFETY: // - We have exclusive access to the world, so no other code can be aliasing the `Ptr` // - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one - changed_by: unsafe { _caller.deref_mut() }, + changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, }; Some((component_info, mut_untyped)) @@ -3769,13 +3589,13 @@ impl FromWorld for T { mod tests { use super::{FromWorld, World}; use crate::{ - change_detection::DetectChangesMut, - component::{ComponentDescriptor, ComponentInfo, StorageType}, + change_detection::{DetectChangesMut, MaybeLocation}, + component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, entity::hash_set::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled}, ptr::OwningPtr, resource::Resource, - world::error::EntityFetchError, + world::error::EntityMutableFetchError, }; use alloc::{ borrow::ToOwned, @@ -3793,9 +3613,6 @@ mod tests { }; use std::{println, sync::Mutex}; - // For bevy_ecs_macros - use crate as bevy_ecs; - type ID = u8; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -4035,12 +3852,7 @@ mod tests { OwningPtr::make(value, |ptr| { // SAFETY: value is valid for the layout of `TestResource` unsafe { - world.insert_resource_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - panic::Location::caller(), - ); + world.insert_resource_by_id(component_id, ptr, MaybeLocation::caller()); } }); @@ -4074,6 +3886,7 @@ mod tests { DROP_COUNT.fetch_add(1, Ordering::SeqCst); }), true, + ComponentCloneBehavior::Default, ) }; @@ -4083,12 +3896,7 @@ mod tests { OwningPtr::make(value, |ptr| { // SAFETY: value is valid for the component layout unsafe { - world.insert_resource_by_id( - component_id, - ptr, - #[cfg(feature = "track_location")] - panic::Location::caller(), - ); + world.insert_resource_by_id(component_id, ptr, MaybeLocation::caller()); } }); @@ -4172,39 +3980,39 @@ mod tests { let bar_id = TypeId::of::(); let baz_id = TypeId::of::(); assert_eq!( - to_type_ids(world.inspect_entity(ent0).collect()), + to_type_ids(world.inspect_entity(ent0).unwrap().collect()), [Some(foo_id), Some(bar_id), Some(baz_id)] .into_iter() .collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent1).collect()), + to_type_ids(world.inspect_entity(ent1).unwrap().collect()), [Some(foo_id), Some(bar_id)] .into_iter() .collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent2).collect()), + to_type_ids(world.inspect_entity(ent2).unwrap().collect()), [Some(bar_id), Some(baz_id)] .into_iter() .collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent3).collect()), + to_type_ids(world.inspect_entity(ent3).unwrap().collect()), [Some(foo_id), Some(baz_id)] .into_iter() .collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent4).collect()), + to_type_ids(world.inspect_entity(ent4).unwrap().collect()), [Some(foo_id)].into_iter().collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent5).collect()), + to_type_ids(world.inspect_entity(ent5).unwrap().collect()), [Some(bar_id)].into_iter().collect::>() ); assert_eq!( - to_type_ids(world.inspect_entity(ent6).collect()), + to_type_ids(world.inspect_entity(ent6).unwrap().collect()), [Some(baz_id)].into_iter().collect::>() ); } @@ -4351,20 +4159,34 @@ mod tests { world.entity_mut(e1).despawn(); - assert_eq!(Err(e1), world.get_entity(e1).map(|_| {})); - assert_eq!(Err(e1), world.get_entity([e1, e2]).map(|_| {})); + assert_eq!( + Err(e1), + world.get_entity(e1).map(|_| {}).map_err(|e| e.entity) + ); + assert_eq!( + Err(e1), + world.get_entity([e1, e2]).map(|_| {}).map_err(|e| e.entity) + ); assert_eq!( Err(e1), world .get_entity(&[e1, e2] /* this is an array not a slice */) .map(|_| {}) + .map_err(|e| e.entity) + ); + assert_eq!( + Err(e1), + world + .get_entity(&vec![e1, e2][..]) + .map(|_| {}) + .map_err(|e| e.entity) ); - assert_eq!(Err(e1), world.get_entity(&vec![e1, e2][..]).map(|_| {})); assert_eq!( Err(e1), world .get_entity(&EntityHashSet::from_iter([e1, e2])) .map(|_| {}) + .map_err(|e| e.entity) ); } @@ -4386,17 +4208,17 @@ mod tests { .is_ok()); assert_eq!( - Err(EntityFetchError::AliasedMutability(e1)), + Err(EntityMutableFetchError::AliasedMutability(e1)), world.get_entity_mut([e1, e2, e1]).map(|_| {}) ); assert_eq!( - Err(EntityFetchError::AliasedMutability(e1)), + Err(EntityMutableFetchError::AliasedMutability(e1)), world .get_entity_mut(&[e1, e2, e1] /* this is an array not a slice */) .map(|_| {}) ); assert_eq!( - Err(EntityFetchError::AliasedMutability(e1)), + Err(EntityMutableFetchError::AliasedMutability(e1)), world.get_entity_mut(&vec![e1, e2, e1][..]).map(|_| {}) ); // Aliased mutability isn't allowed by HashSets @@ -4408,28 +4230,27 @@ mod tests { assert!(matches!( world.get_entity_mut(e1).map(|_| {}), - Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1 + Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1 )); assert!(matches!( world.get_entity_mut([e1, e2]).map(|_| {}), - Err(EntityFetchError::NoSuchEntity(e,..)) if e == e1)); + Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); assert!(matches!( world .get_entity_mut(&[e1, e2] /* this is an array not a slice */) .map(|_| {}), - Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1)); + Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); assert!(matches!( world.get_entity_mut(&vec![e1, e2][..]).map(|_| {}), - Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1, + Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1, )); assert!(matches!( world .get_entity_mut(&EntityHashSet::from_iter([e1, e2])) .map(|_| {}), - Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1)); + Err(EntityMutableFetchError::EntityDoesNotExist(e)) if e.entity == e1)); } - #[cfg(feature = "track_location")] #[test] #[track_caller] fn entity_spawn_despawn_tracking() { @@ -4439,23 +4260,23 @@ mod tests { let entity = world.spawn_empty().id(); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), - Some(Location::caller()) + MaybeLocation::new(Some(Location::caller())) ); world.despawn(entity); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), - Some(Location::caller()) + MaybeLocation::new(Some(Location::caller())) ); let new = world.spawn_empty().id(); assert_eq!(entity.index(), new.index()); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), - None + MaybeLocation::new(None) ); world.despawn(new); assert_eq!( world.entities.entity_get_spawned_or_despawned_by(entity), - None + MaybeLocation::new(None) ); } diff --git a/crates/bevy_ecs/src/world/reflect.rs b/crates/bevy_ecs/src/world/reflect.rs index 882cf9b74a78a..4337416aa2dc9 100644 --- a/crates/bevy_ecs/src/world/reflect.rs +++ b/crates/bevy_ecs/src/world/reflect.rs @@ -251,11 +251,7 @@ mod tests { use bevy_reflect::Reflect; - use crate::{ - // For bevy_ecs_macros - self as bevy_ecs, - prelude::{AppTypeRegistry, Component, DetectChanges, World}, - }; + use crate::prelude::{AppTypeRegistry, Component, DetectChanges, World}; #[derive(Component, Reflect)] struct RFoo(i32); diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index eaa8cf7b9c889..16bd9bb8059b4 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -1,11 +1,10 @@ use crate::{ - bundle::{Bundle, BundleSpawner}, + bundle::{Bundle, BundleSpawner, NoBundleEffect}, + change_detection::MaybeLocation, entity::{Entity, EntitySetIterator}, world::World, }; use core::iter::FusedIterator; -#[cfg(feature = "track_location")] -use core::panic::Location; /// An iterator that spawns a series of entities and returns the [ID](Entity) of /// each spawned entity. @@ -18,22 +17,17 @@ where { inner: I, spawner: BundleSpawner<'w>, - #[cfg(feature = "track_location")] - caller: &'static Location<'static>, + caller: MaybeLocation, } impl<'w, I> SpawnBatchIter<'w, I> where I: Iterator, - I::Item: Bundle, + I::Item: Bundle, { #[inline] #[track_caller] - pub(crate) fn new( - world: &'w mut World, - iter: I, - #[cfg(feature = "track_location")] caller: &'static Location, - ) -> Self { + pub(crate) fn new(world: &'w mut World, iter: I, caller: MaybeLocation) -> Self { // Ensure all entity allocations are accounted for so `self.entities` can realloc if // necessary world.flush(); @@ -50,7 +44,6 @@ where Self { inner: iter, spawner, - #[cfg(feature = "track_location")] caller, } } @@ -80,13 +73,7 @@ where fn next(&mut self) -> Option { let bundle = self.inner.next()?; // SAFETY: bundle matches spawner type - unsafe { - Some(self.spawner.spawn( - bundle, - #[cfg(feature = "track_location")] - self.caller, - )) - } + unsafe { Some(self.spawner.spawn(bundle, self.caller).0) } } fn size_hint(&self) -> (usize, Option) { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 8486ee950cc07..cbaffb299192c 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -4,9 +4,9 @@ use super::{Mut, Ref, World, WorldId}; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, - change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut}, + change_detection::{MaybeLocation, MutUntyped, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, - entity::{Entities, Entity, EntityBorrow, EntityLocation}, + entity::{Entities, Entity, EntityBorrow, EntityDoesNotExistError, EntityLocation}, observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReadOnlyQueryData}, @@ -16,13 +16,10 @@ use crate::{ world::RawCommandQueue, }; use bevy_platform_support::sync::atomic::Ordering; -use bevy_ptr::Ptr; -use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr}; +use bevy_ptr::{Ptr, UnsafeCellDeref}; +use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; use thiserror::Error; -#[cfg(feature = "track_location")] -use {bevy_ptr::UnsafeCellDeref, core::panic::Location}; - /// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid /// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule. /// @@ -354,9 +351,15 @@ impl<'w> UnsafeWorldCell<'w> { /// 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(self, entity: Entity) -> Option> { - let location = self.entities().get(entity)?; - Some(UnsafeEntityCell::new(self, entity, location)) + pub fn get_entity( + self, + entity: Entity, + ) -> Result, EntityDoesNotExistError> { + let location = self + .entities() + .get(entity) + .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; + Ok(UnsafeEntityCell::new(self, entity, location)) } /// Gets a reference to the resource of the given type if it exists @@ -389,7 +392,7 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: caller ensures `self` has permission to access the resource // caller also ensure that no mutable reference to the resource exists - let (ptr, ticks, _caller) = unsafe { self.get_resource_with_ticks(component_id)? }; + let (ptr, ticks, caller) = unsafe { self.get_resource_with_ticks(component_id)? }; // SAFETY: `component_id` was obtained from the type ID of `R` let value = unsafe { ptr.deref::() }; @@ -399,13 +402,11 @@ impl<'w> UnsafeWorldCell<'w> { unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), self.change_tick()) }; // SAFETY: caller ensures that no mutable reference to the resource exists - #[cfg(feature = "track_location")] - let caller = unsafe { _caller.deref() }; + let caller = caller.map(|caller| unsafe { caller.deref() }); Some(Ref { value, ticks, - #[cfg(feature = "track_location")] changed_by: caller, }) } @@ -512,7 +513,7 @@ impl<'w> UnsafeWorldCell<'w> { self.assert_allows_mutable_access(); // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. - let (ptr, ticks, _caller) = unsafe { self.storages() } + let (ptr, ticks, caller) = unsafe { self.storages() } .resources .get(component_id)? .get_with_ticks()?; @@ -530,11 +531,10 @@ impl<'w> UnsafeWorldCell<'w> { // - caller ensures that the resource is unaliased value: unsafe { ptr.assert_unique() }, ticks, - #[cfg(feature = "track_location")] // SAFETY: // - caller ensures that `self` has permission to access the resource // - caller ensures that the resource is unaliased - changed_by: unsafe { _caller.deref_mut() }, + changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, }) } @@ -581,7 +581,7 @@ impl<'w> UnsafeWorldCell<'w> { let change_tick = self.change_tick(); // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. - let (ptr, ticks, _caller) = unsafe { self.storages() } + let (ptr, ticks, caller) = unsafe { self.storages() } .non_send_resources .get(component_id)? .get_with_ticks()?; @@ -596,9 +596,8 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. value: unsafe { ptr.assert_unique() }, ticks, - #[cfg(feature = "track_location")] // SAFETY: This function has exclusive access to the world - changed_by: unsafe { _caller.deref_mut() }, + changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, }) } @@ -611,7 +610,11 @@ impl<'w> UnsafeWorldCell<'w> { pub(crate) unsafe fn get_resource_with_ticks( self, component_id: ComponentId, - ) -> Option<(Ptr<'w>, TickCells<'w>, MaybeUnsafeCellLocation<'w>)> { + ) -> Option<( + Ptr<'w>, + TickCells<'w>, + MaybeLocation<&'w UnsafeCell<&'static Location<'static>>>, + )> { // SAFETY: // - caller ensures there is no `&mut World` // - caller ensures there are no mutable borrows of this resource @@ -634,7 +637,11 @@ impl<'w> UnsafeWorldCell<'w> { pub(crate) unsafe fn get_non_send_with_ticks( self, component_id: ComponentId, - ) -> Option<(Ptr<'w>, TickCells<'w>, MaybeUnsafeCellLocation<'w>)> { + ) -> Option<( + Ptr<'w>, + TickCells<'w>, + MaybeLocation<&'w UnsafeCell<&'static Location<'static>>>, + )> { // SAFETY: // - caller ensures there is no `&mut World` // - caller ensures there are no mutable borrows of this resource @@ -808,12 +815,11 @@ impl<'w> UnsafeEntityCell<'w> { self.entity, self.location, ) - .map(|(value, cells, _caller)| Ref { + .map(|(value, cells, caller)| Ref { // SAFETY: returned component is of type T value: value.deref::(), ticks: Ticks::from_tick_cells(cells, last_change_tick, change_tick), - #[cfg(feature = "track_location")] - changed_by: _caller.deref(), + changed_by: caller.map(|caller| caller.deref()), }) } } @@ -930,12 +936,11 @@ impl<'w> UnsafeEntityCell<'w> { self.entity, self.location, ) - .map(|(value, cells, _caller)| Mut { + .map(|(value, cells, caller)| Mut { // SAFETY: returned component is of type T value: value.assert_unique().deref_mut::(), ticks: TicksMut::from_tick_cells(cells, last_change_tick, change_tick), - #[cfg(feature = "track_location")] - changed_by: _caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), }) } } @@ -1054,7 +1059,7 @@ impl<'w> UnsafeEntityCell<'w> { self.entity, self.location, ) - .map(|(value, cells, _caller)| MutUntyped { + .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( @@ -1062,20 +1067,18 @@ impl<'w> UnsafeEntityCell<'w> { self.world.last_change_tick(), self.world.change_tick(), ), - #[cfg(feature = "track_location")] - changed_by: _caller.deref_mut(), + changed_by: caller.map(|caller| caller.deref_mut()), }) .ok_or(GetEntityMutByIdError::ComponentNotFound) } } /// Returns the source code location from which this entity has been spawned. - #[cfg(feature = "track_location")] - pub fn spawned_by(self) -> &'static Location<'static> { + pub fn spawned_by(self) -> MaybeLocation { self.world() .entities() .entity_get_spawned_or_despawned_by(self.entity) - .unwrap() + .map(|o| o.unwrap()) } } @@ -1159,7 +1162,11 @@ unsafe fn get_component_and_ticks( storage_type: StorageType, entity: Entity, location: EntityLocation, -) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { +) -> Option<( + Ptr<'_>, + TickCells<'_>, + MaybeLocation<&UnsafeCell<&'static Location<'static>>>, +)> { match storage_type { StorageType::Table => { let table = world.fetch_table(location)?; @@ -1175,12 +1182,9 @@ unsafe fn get_component_and_ticks( .get_changed_tick(component_id, location.table_row) .debug_checked_unwrap(), }, - #[cfg(feature = "track_location")] table .get_changed_by(component_id, location.table_row) - .debug_checked_unwrap(), - #[cfg(not(feature = "track_location"))] - (), + .map(|changed_by| changed_by.debug_checked_unwrap()), )) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_with_ticks(entity), @@ -1222,7 +1226,6 @@ impl EntityBorrow for UnsafeEntityCell<'_> { #[cfg(test)] mod tests { use super::*; - use crate as bevy_ecs; #[test] #[should_panic = "is forbidden"] diff --git a/crates/bevy_encase_derive/LICENSE-APACHE b/crates/bevy_encase_derive/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_encase_derive/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_encase_derive/LICENSE-MIT b/crates/bevy_encase_derive/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_encase_derive/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_encase_derive/src/lib.rs b/crates/bevy_encase_derive/src/lib.rs index 0432d161f0a6b..eccfcdd01df52 100644 --- a/crates/bevy_encase_derive/src/lib.rs +++ b/crates/bevy_encase_derive/src/lib.rs @@ -31,7 +31,7 @@ fn bevy_encase_path() -> syn::Path { segments, } }) - .unwrap_or_else(|| bevy_manifest.get_path(ENCASE)) + .unwrap_or_else(|_err| bevy_manifest.get_path(ENCASE)) } implement!(bevy_encase_path()); diff --git a/crates/bevy_gilrs/LICENSE-APACHE b/crates/bevy_gilrs/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_gilrs/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_gilrs/LICENSE-MIT b/crates/bevy_gilrs/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_gilrs/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_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index bd692766ec016..b5f1421493cd3 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -23,8 +23,7 @@ pub fn gilrs_event_startup_system( let entity = commands.spawn_empty().id(); gamepads.id_to_entity.insert(id, entity); gamepads.entity_to_id.insert(entity, id); - - events.send(GamepadConnectionEvent { + events.write(GamepadConnectionEvent { gamepad: entity, connection: GamepadConnection::Connected { name: gamepad.name().to_string(), @@ -66,9 +65,8 @@ pub fn gilrs_event_system( product_id: pad.product_id(), }, ); - - events.send(event.clone().into()); - connection_events.send(event); + events.write(event.clone().into()); + connection_events.write(event); } EventType::Disconnected => { let gamepad = gamepads @@ -76,10 +74,9 @@ pub fn gilrs_event_system( .get(&gilrs_event.id) .copied() .expect("mapping should exist from connection"); - let event = - GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected); - events.send(event.clone().into()); - connection_events.send(event); + let event = GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected); + events.write(event.clone().into()); + connection_events.write(event); } EventType::ButtonChanged(gilrs_button, raw_value, _) => { let Some(button) = convert_button(gilrs_button) else { @@ -90,9 +87,8 @@ pub fn gilrs_event_system( .get(&gilrs_event.id) .copied() .expect("mapping should exist from connection"); - events - .send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(RawGamepadButtonChangedEvent::new( + events.write(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.write(RawGamepadButtonChangedEvent::new( gamepad, button, raw_value, )); } @@ -105,8 +101,8 @@ pub fn gilrs_event_system( .get(&gilrs_event.id) .copied() .expect("mapping should exist from connection"); - events.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); + events.write(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.write(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); } _ => (), }; diff --git a/crates/bevy_gizmos/LICENSE-APACHE b/crates/bevy_gizmos/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_gizmos/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_gizmos/LICENSE-MIT b/crates/bevy_gizmos/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_gizmos/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_gizmos/macros/LICENSE-APACHE b/crates/bevy_gizmos/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_gizmos/macros/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_gizmos/macros/LICENSE-MIT b/crates/bevy_gizmos/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_gizmos/macros/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_gizmos/src/aabb.rs b/crates/bevy_gizmos/src/aabb.rs index b10b2bc946966..605a4c2e88c6a 100644 --- a/crates/bevy_gizmos/src/aabb.rs +++ b/crates/bevy_gizmos/src/aabb.rs @@ -1,7 +1,5 @@ //! A module adding debug visualization of [`Aabb`]s. -use crate as bevy_gizmos; - use bevy_app::{Plugin, PostUpdate}; use bevy_color::{Color, Oklcha}; use bevy_ecs::{ diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index a7e60088b6d7b..bba3ff284cb2e 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -1,6 +1,5 @@ //! A module for the [`GizmoConfig`] [`Resource`]. -use crate::{self as bevy_gizmos}; pub use bevy_gizmos_macros::GizmoConfigGroup; #[cfg(all( diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 2a7baab6dfa0a..fdc2243233916 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -19,6 +19,9 @@ //! //! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples. +// Required to make proc macros work in bevy itself. +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 { @@ -76,7 +79,7 @@ pub mod prelude { } use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop}; -use bevy_asset::{Asset, AssetApp, AssetId, Assets, Handle}; +use bevy_asset::{weak_handle, Asset, AssetApp, AssetId, Assets, Handle}; use bevy_ecs::{ resource::Resource, schedule::{IntoSystemConfigs, SystemSet}, @@ -138,9 +141,10 @@ use gizmos::{GizmoStorage, Swap}; use light::LightGizmoPlugin; #[cfg(feature = "bevy_render")] -const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); +const LINE_SHADER_HANDLE: Handle = weak_handle!("15dc5869-ad30-4664-b35a-4137cb8804a1"); #[cfg(feature = "bevy_render")] -const LINE_JOINT_SHADER_HANDLE: Handle = Handle::weak_from_u128(1162780797909187908); +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. /// diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs index 9a618ac85acc7..d6df0b7d3bb20 100644 --- a/crates/bevy_gizmos/src/light.rs +++ b/crates/bevy_gizmos/src/light.rs @@ -2,7 +2,7 @@ use core::f32::consts::PI; -use crate::{self as bevy_gizmos, primitives::dim3::GizmoPrimitive3d}; +use crate::primitives::dim3::GizmoPrimitive3d; use bevy_app::{Plugin, PostUpdate}; use bevy_color::{ diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index b7bca04797a35..da0d97ad9670a 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -15,7 +15,7 @@ pbr_multi_layer_material_textures = [ "bevy_pbr/pbr_multi_layer_material_textures", ] pbr_anisotropy_texture = ["bevy_pbr/pbr_anisotropy_texture"] -pbr_specular_textures = [] +pbr_specular_textures = ["bevy_pbr/pbr_specular_textures"] [dependencies] # bevy diff --git a/crates/bevy_gltf/LICENSE-APACHE b/crates/bevy_gltf/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_gltf/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_gltf/LICENSE-MIT b/crates/bevy_gltf/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_gltf/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_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 74664b3e37571..27f0ad00bbc47 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -492,13 +492,13 @@ pub struct GltfMaterialName(pub String); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GltfAssetLabel { - /// `Scene{}`: glTF Scene as a Bevy `Scene` + /// `Scene{}`: glTF Scene as a Bevy [`Scene`] Scene(usize), - /// `Node{}`: glTF Node as a `GltfNode` + /// `Node{}`: glTF Node as a [`GltfNode`] Node(usize), - /// `Mesh{}`: glTF Mesh as a `GltfMesh` + /// `Mesh{}`: glTF Mesh as a [`GltfMesh`] Mesh(usize), - /// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy `Mesh` + /// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`] Primitive { /// Index of the mesh for this primitive mesh: usize, @@ -506,28 +506,29 @@ pub enum GltfAssetLabel { primitive: usize, }, /// `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive + /// as a Bevy [`Image`](bevy_image::prelude::Image) MorphTarget { /// Index of the mesh for this primitive mesh: usize, /// Index of this primitive in its parent mesh primitive: usize, }, - /// `Texture{}`: glTF Texture as a Bevy `Image` + /// `Texture{}`: glTF Texture as a Bevy [`Image`](bevy_image::prelude::Image) Texture(usize), - /// `Material{}`: glTF Material as a Bevy `StandardMaterial` + /// `Material{}`: glTF Material as a Bevy [`StandardMaterial`] Material { /// Index of this material index: usize, /// Used to set the [`Face`](bevy_render::render_resource::Face) of the material, useful if it is used with negative scale is_scale_inverted: bool, }, - /// `DefaultMaterial`: as above, if the glTF file contains a default material with no index + /// `DefaultMaterial`: glTF's default Material as a Bevy [`StandardMaterial`] DefaultMaterial, - /// `Animation{}`: glTF Animation as Bevy `AnimationClip` + /// `Animation{}`: glTF Animation as Bevy [`AnimationClip`] Animation(usize), - /// `Skin{}`: glTF mesh skin as `GltfSkin` + /// `Skin{}`: glTF mesh skin as [`GltfSkin`] Skin(usize), - /// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy `SkinnedMeshInverseBindposes` + /// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy [`SkinnedMeshInverseBindposes`] InverseBindMatrices(usize), } diff --git a/crates/bevy_image/Cargo.toml b/crates/bevy_image/Cargo.toml index 7e0f825fae0cb..ceab1de1ce33e 100644 --- a/crates/bevy_image/Cargo.toml +++ b/crates/bevy_image/Cargo.toml @@ -16,7 +16,7 @@ bevy_reflect = ["dep:bevy_reflect", "bevy_math/bevy_reflect"] # Image formats basis-universal = ["dep:basis-universal"] bmp = ["image/bmp"] -dds = ["ddsfile"] +dds = ["ddsfile", "bevy_core_pipeline/dds"] exr = ["image/exr"] ff = ["image/ff"] gif = ["image/gif"] @@ -63,9 +63,7 @@ image = { version = "0.25.2", default-features = false } # misc bitflags = { version = "2.3", features = ["serde"] } bytemuck = { version = "1.5" } -wgpu-types = { version = "23", default-features = false } -# TODO: remove dependency on wgpu once https://github.com/gfx-rs/wgpu/pull/6648, 6649 and 6650 have been released -wgpu = { version = "23.0.1", default-features = false } +wgpu-types = { version = "24", default-features = false } serde = { version = "1", features = ["derive"] } thiserror = { version = "2", default-features = false } futures-lite = "2.0.1" @@ -84,6 +82,7 @@ half = { version = "2.4.1" } [dev-dependencies] bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_sprite = { path = "../bevy_sprite", version = "0.16.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } [lints] workspace = true diff --git a/crates/bevy_image/LICENSE-APACHE b/crates/bevy_image/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_image/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_image/LICENSE-MIT b/crates/bevy_image/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_image/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_image/src/basis.rs b/crates/bevy_image/src/basis.rs index 7772f38c0b952..553140b0024bf 100644 --- a/crates/bevy_image/src/basis.rs +++ b/crates/bevy_image/src/basis.rs @@ -116,7 +116,7 @@ pub fn basis_buffer_to_image( ))) } }; - image.data = transcoded; + image.data = Some(transcoded); Ok(image) } diff --git a/crates/bevy_image/src/compressed_image_saver.rs b/crates/bevy_image/src/compressed_image_saver.rs index 4bd68fdafca6c..c2adc2c029116 100644 --- a/crates/bevy_image/src/compressed_image_saver.rs +++ b/crates/bevy_image/src/compressed_image_saver.rs @@ -11,6 +11,8 @@ pub struct CompressedImageSaver; pub enum CompressedImageSaverError { #[error(transparent)] Io(#[from] std::io::Error), + #[error("Cannot compress an uninitialized image")] + UninitializedImage, } impl AssetSaver for CompressedImageSaver { @@ -42,7 +44,10 @@ impl AssetSaver for CompressedImageSaver { let mut source_image = compressor_params.source_image_mut(0); let size = image.size(); - source_image.init(&image.data, size.x, size.y, 4); + let Some(ref data) = image.data else { + return Err(CompressedImageSaverError::UninitializedImage); + }; + source_image.init(data, size.x, size.y, 4); let mut compressor = basis_universal::Compressor::new(4); #[expect( diff --git a/crates/bevy_image/src/dds.rs b/crates/bevy_image/src/dds.rs index ffb3ce2c7c118..15d753008229c 100644 --- a/crates/bevy_image/src/dds.rs +++ b/crates/bevy_image/src/dds.rs @@ -2,8 +2,9 @@ use ddsfile::{Caps2, D3DFormat, Dds, DxgiFormat}; use std::io::Cursor; -use wgpu::TextureViewDescriptor; -use wgpu_types::{Extent3d, TextureDimension, TextureFormat, TextureViewDimension}; +use wgpu_types::{ + Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension, +}; #[cfg(debug_assertions)] use {bevy_utils::once, tracing::warn}; @@ -65,10 +66,14 @@ pub fn dds_buffer_to_image( image.texture_descriptor.format = texture_format; image.texture_descriptor.dimension = if dds.get_depth() > 1 { TextureDimension::D3 - } else if image.is_compressed() || dds.get_height() > 1 { - TextureDimension::D2 - } else { + // 1x1 textures should generally be interpreted as solid 2D + } else if ((dds.get_width() > 1 || dds.get_height() > 1) + && !(dds.get_width() > 1 && dds.get_height() > 1)) + && !image.is_compressed() + { TextureDimension::D1 + } else { + TextureDimension::D2 }; if is_cubemap { let dimension = if image.texture_descriptor.size.depth_or_array_layers > 6 { @@ -81,7 +86,7 @@ pub fn dds_buffer_to_image( ..Default::default() }); } - image.data = dds.data; + image.data = Some(dds.data); Ok(image) } @@ -283,8 +288,7 @@ pub fn dds_format_to_texture_format( #[cfg(test)] mod test { - use wgpu::util::TextureDataOrder; - use wgpu_types::{TextureDescriptor, TextureDimension, TextureFormat}; + use wgpu_types::{TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat}; use crate::CompressedImageFormats; @@ -373,7 +377,7 @@ mod test { let r = dds_buffer_to_image("".into(), &buffer, CompressedImageFormats::BC, true); assert!(r.is_ok()); if let Ok(r) = r { - fake_wgpu_create_texture_with_data(&r.texture_descriptor, &r.data); + fake_wgpu_create_texture_with_data(&r.texture_descriptor, r.data.as_ref().unwrap()); } } } diff --git a/crates/bevy_image/src/dynamic_texture_atlas_builder.rs b/crates/bevy_image/src/dynamic_texture_atlas_builder.rs index 8944e74e74157..6e013aba29a45 100644 --- a/crates/bevy_image/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_image/src/dynamic_texture_atlas_builder.rs @@ -2,6 +2,20 @@ use crate::{Image, TextureAtlasLayout, TextureFormatPixelInfo as _}; use bevy_asset::RenderAssetUsages; use bevy_math::{URect, UVec2}; use guillotiere::{size2, Allocation, AtlasAllocator}; +use thiserror::Error; +use tracing::error; + +#[derive(Debug, Error)] +pub enum DynamicTextureAtlasBuilderError { + #[error("Couldn't allocate space to add the image requested")] + FailedToAllocateSpace, + /// Attempted to add a texture to an uninitialzied atlas + #[error("cannot add texture to uninitialized atlas texture")] + UninitializedAtlas, + /// Attempted to add an uninitialized texture to an atlas + #[error("cannot add uninitialized texture to atlas")] + UninitializedSourceTexture, +} /// Helper utility to update [`TextureAtlasLayout`] on the fly. /// @@ -42,7 +56,7 @@ impl DynamicTextureAtlasBuilder { atlas_layout: &mut TextureAtlasLayout, texture: &Image, atlas_texture: &mut Image, - ) -> Option { + ) -> Result { let allocation = self.atlas_allocator.allocate(size2( (texture.width() + self.padding).try_into().unwrap(), (texture.height() + self.padding).try_into().unwrap(), @@ -53,12 +67,12 @@ impl DynamicTextureAtlasBuilder { "The atlas_texture image must have the RenderAssetUsages::MAIN_WORLD usage flag set" ); - self.place_texture(atlas_texture, allocation, texture); + self.place_texture(atlas_texture, allocation, texture)?; let mut rect: URect = to_rect(allocation.rectangle); rect.max = rect.max.saturating_sub(UVec2::splat(self.padding)); - Some(atlas_layout.add_texture(rect)) + Ok(atlas_layout.add_texture(rect)) } else { - None + Err(DynamicTextureAtlasBuilderError::FailedToAllocateSpace) } } @@ -67,7 +81,7 @@ impl DynamicTextureAtlasBuilder { atlas_texture: &mut Image, allocation: Allocation, texture: &Image, - ) { + ) -> Result<(), DynamicTextureAtlasBuilderError> { let mut rect = allocation.rectangle; rect.max.x -= self.padding as i32; rect.max.y -= self.padding as i32; @@ -75,14 +89,20 @@ impl DynamicTextureAtlasBuilder { let rect_width = rect.width() as usize; let format_size = atlas_texture.texture_descriptor.format.pixel_size(); + let Some(ref mut atlas_data) = atlas_texture.data else { + return Err(DynamicTextureAtlasBuilderError::UninitializedAtlas); + }; + let Some(ref data) = texture.data else { + return Err(DynamicTextureAtlasBuilderError::UninitializedSourceTexture); + }; for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() { let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size; let end = begin + rect_width * format_size; let texture_begin = texture_y * rect_width * format_size; let texture_end = texture_begin + rect_width * format_size; - atlas_texture.data[begin..end] - .copy_from_slice(&texture.data[texture_begin..texture_end]); + atlas_data[begin..end].copy_from_slice(&data[texture_begin..texture_end]); } + Ok(()) } } diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 587a4d8a5a1ed..e9a1ef2ddc420 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -13,10 +13,11 @@ use bevy_math::{AspectRatio, UVec2, UVec3, Vec2}; use core::hash::Hash; use serde::{Deserialize, Serialize}; use thiserror::Error; -use wgpu::{SamplerDescriptor, TextureViewDescriptor}; +use tracing::warn; use wgpu_types::{ AddressMode, CompareFunction, Extent3d, Features, FilterMode, SamplerBorderColor, - TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + TextureViewDescriptor, }; pub trait BevyDefault { @@ -338,12 +339,16 @@ impl ImageFormat { reflect(opaque, Default, Debug) )] pub struct Image { - pub data: Vec, + /// 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` + pub data: Option>, // 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, - pub texture_view_descriptor: Option>, + pub texture_view_descriptor: Option>>, pub asset_usage: RenderAssetUsages, } @@ -559,7 +564,7 @@ impl ImageSamplerDescriptor { } } - pub fn as_wgpu(&self) -> SamplerDescriptor { + pub fn as_wgpu(&self) -> SamplerDescriptor> { SamplerDescriptor { label: self.label.as_deref(), address_mode_u: self.address_mode_u.into(), @@ -669,8 +674,8 @@ impl From for ImageSamplerBorderColor { } } -impl<'a> From> for ImageSamplerDescriptor { - fn from(value: SamplerDescriptor) -> Self { +impl From>> for ImageSamplerDescriptor { + fn from(value: SamplerDescriptor>) -> Self { ImageSamplerDescriptor { label: value.label.map(ToString::to_string), address_mode_u: value.address_mode_u.into(), @@ -691,28 +696,9 @@ impl<'a> From> for ImageSamplerDescriptor { impl Default for Image { /// default is a 1x1x1 all '1.0' texture fn default() -> Self { - let format = TextureFormat::bevy_default(); - let data = vec![255; format.pixel_size()]; - Image { - data, - texture_descriptor: TextureDescriptor { - size: Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - format, - dimension: TextureDimension::D2, - label: None, - mip_level_count: 1, - sample_count: 1, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - view_formats: &[], - }, - sampler: ImageSampler::Default, - texture_view_descriptor: None, - asset_usage: RenderAssetUsages::default(), - } + let mut image = Image::default_uninit(); + image.data = Some(vec![255; image.texture_descriptor.format.pixel_size()]); + image } } @@ -734,37 +720,24 @@ impl Image { data.len(), "Pixel data, size and format have to match", ); - let mut image = Self { - data, - ..Default::default() - }; - image.texture_descriptor.dimension = dimension; - image.texture_descriptor.size = size; - image.texture_descriptor.format = format; - image.asset_usage = asset_usage; + let mut image = Image::new_uninit(size, dimension, format, asset_usage); + image.data = Some(data); image } - /// A transparent white 1x1x1 image. - /// - /// Contrast to [`Image::default`], which is opaque. - pub fn transparent() -> Image { - // We rely on the default texture format being RGBA8UnormSrgb - // when constructing a transparent color from bytes. - // If this changes, this function will need to be updated. - let format = TextureFormat::bevy_default(); - debug_assert!(format.pixel_size() == 4); - let data = vec![255, 255, 255, 0]; + /// Exactly the same as [`Image::new`], but doesn't initialize the image + pub fn new_uninit( + size: Extent3d, + dimension: TextureDimension, + format: TextureFormat, + asset_usage: RenderAssetUsages, + ) -> Self { Image { - data, + data: None, texture_descriptor: TextureDescriptor { - size: Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, + size, format, - dimension: TextureDimension::D2, + dimension, label: None, mip_level_count: 1, sample_count: 1, @@ -773,10 +746,46 @@ impl Image { }, sampler: ImageSampler::Default, texture_view_descriptor: None, - asset_usage: RenderAssetUsages::default(), + asset_usage, } } + /// A transparent white 1x1x1 image. + /// + /// Contrast to [`Image::default`], which is opaque. + pub fn transparent() -> Image { + // We rely on the default texture format being RGBA8UnormSrgb + // when constructing a transparent color from bytes. + // If this changes, this function will need to be updated. + let format = TextureFormat::bevy_default(); + debug_assert!(format.pixel_size() == 4); + let data = vec![255, 255, 255, 0]; + Image::new( + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + data, + format, + RenderAssetUsages::default(), + ) + } + /// Creates a new uninitialized 1x1x1 image + pub fn default_uninit() -> Image { + Image::new_uninit( + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + TextureFormat::bevy_default(), + RenderAssetUsages::default(), + ) + } + /// Creates a new image from raw binary data and the corresponding metadata, by filling /// the image data with the `pixel` data repeated multiple times. /// @@ -789,12 +798,7 @@ impl Image { format: TextureFormat, asset_usage: RenderAssetUsages, ) -> Self { - let mut value = Image::default(); - value.texture_descriptor.format = format; - value.texture_descriptor.dimension = dimension; - value.asset_usage = asset_usage; - value.resize(size); - + let byte_len = format.pixel_size() * size.volume(); debug_assert_eq!( pixel.len() % format.pixel_size(), 0, @@ -802,15 +806,12 @@ impl Image { format.pixel_size(), ); debug_assert!( - pixel.len() <= value.data.len(), + pixel.len() <= byte_len, "Fill data must fit within pixel buffer (expected {}B).", - value.data.len(), + byte_len, ); - - for current_pixel in value.data.chunks_exact_mut(pixel.len()) { - current_pixel.copy_from_slice(pixel); - } - value + let data = pixel.iter().copied().cycle().take(byte_len).collect(); + Image::new(size, dimension, data, format, asset_usage) } /// Returns the width of a 2D image. @@ -849,10 +850,14 @@ impl Image { /// Does not properly resize the contents of the image, but only its internal `data` buffer. pub fn resize(&mut self, size: Extent3d) { self.texture_descriptor.size = size; - self.data.resize( - size.volume() * self.texture_descriptor.format.pixel_size(), - 0, - ); + if let Some(ref mut data) = self.data { + data.resize( + size.volume() * self.texture_descriptor.format.pixel_size(), + 0, + ); + } else { + warn!("Resized an uninitialized image. Directly modify image.texture_descriptor.size instead"); + } } /// Changes the `size`, asserting that the total number of data elements (pixels) remains the @@ -1035,16 +1040,18 @@ impl Image { #[inline(always)] pub fn pixel_bytes(&self, coords: UVec3) -> Option<&[u8]> { let len = self.texture_descriptor.format.pixel_size(); + let data = self.data.as_ref()?; self.pixel_data_offset(coords) - .map(|start| &self.data[start..(start + len)]) + .map(|start| &data[start..(start + len)]) } /// Get a mutable reference to the data bytes where a specific pixel's value is stored #[inline(always)] pub fn pixel_bytes_mut(&mut self, coords: UVec3) -> Option<&mut [u8]> { let len = self.texture_descriptor.format.pixel_size(); - self.pixel_data_offset(coords) - .map(|start| &mut self.data[start..(start + len)]) + let offset = self.pixel_data_offset(coords); + let data = self.data.as_mut()?; + offset.map(|start| &mut data[start..(start + len)]) } /// Read the color of a specific pixel (1D texture). diff --git a/crates/bevy_image/src/image_texture_conversion.rs b/crates/bevy_image/src/image_texture_conversion.rs index 7956e810cba0b..1eb3b78b1eaff 100644 --- a/crates/bevy_image/src/image_texture_conversion.rs +++ b/crates/bevy_image/src/image_texture_conversion.rs @@ -170,22 +170,26 @@ impl Image { /// /// To convert [`Image`] to a different format see: [`Image::convert`]. pub fn try_into_dynamic(self) -> Result { + let width = self.width(); + let height = self.height(); + let Some(data) = self.data else { + return Err(IntoDynamicImageError::UninitializedImage); + }; match self.texture_descriptor.format { - TextureFormat::R8Unorm => ImageBuffer::from_raw(self.width(), self.height(), self.data) - .map(DynamicImage::ImageLuma8), + TextureFormat::R8Unorm => { + ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLuma8) + } TextureFormat::Rg8Unorm => { - ImageBuffer::from_raw(self.width(), self.height(), self.data) - .map(DynamicImage::ImageLumaA8) + ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLumaA8) } TextureFormat::Rgba8UnormSrgb => { - ImageBuffer::from_raw(self.width(), self.height(), self.data) - .map(DynamicImage::ImageRgba8) + ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageRgba8) } // This format is commonly used as the format for the swapchain texture // This conversion is added here to support screenshots TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => { - ImageBuffer::from_raw(self.width(), self.height(), { - let mut data = self.data; + ImageBuffer::from_raw(width, height, { + let mut data = data; for bgra in data.chunks_exact_mut(4) { bgra.swap(0, 2); } @@ -213,6 +217,10 @@ pub enum IntoDynamicImageError { /// Encountered an unknown error during conversion. #[error("Failed to convert into {0:?}.")] UnknownConversionError(TextureFormat), + + /// Tried to convert an image that has no texture data + #[error("Image has no texture data")] + UninitializedImage, } #[cfg(test)] diff --git a/crates/bevy_image/src/ktx2.rs b/crates/bevy_image/src/ktx2.rs index ff89a95ef218b..a45b77a2a8fc9 100644 --- a/crates/bevy_image/src/ktx2.rs +++ b/crates/bevy_image/src/ktx2.rs @@ -13,9 +13,9 @@ use ktx2::{ BasicDataFormatDescriptor, ChannelTypeQualifiers, ColorModel, DataFormatDescriptorHeader, Header, SampleInformation, }; -use wgpu::TextureViewDescriptor; use wgpu_types::{ - AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat, TextureViewDimension, + AstcBlock, AstcChannel, Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, + TextureViewDimension, }; use super::{CompressedImageFormats, DataFormat, Image, TextureError, TranscodeFormat}; @@ -266,7 +266,7 @@ pub fn ktx2_buffer_to_image( // error cases have been handled let mut image = Image::default(); image.texture_descriptor.format = texture_format; - image.data = wgpu_data.into_iter().flatten().collect::>(); + image.data = Some(wgpu_data.into_iter().flatten().collect::>()); image.texture_descriptor.size = Extent3d { width, height, diff --git a/crates/bevy_image/src/texture_atlas_builder.rs b/crates/bevy_image/src/texture_atlas_builder.rs index a207b318f75b0..443660eefc424 100644 --- a/crates/bevy_image/src/texture_atlas_builder.rs +++ b/crates/bevy_image/src/texture_atlas_builder.rs @@ -18,6 +18,12 @@ pub enum TextureAtlasBuilderError { NotEnoughSpace, #[error("added a texture with the wrong format in an atlas")] WrongFormat, + /// Attempted to add a texture to an uninitialzied atlas + #[error("cannot add texture to uninitialized atlas texture")] + UninitializedAtlas, + /// Attempted to add an uninitialized texture to an atlas + #[error("cannot add uninitialized texture to atlas")] + UninitializedSourceTexture, } #[derive(Debug)] @@ -105,7 +111,7 @@ impl<'a> TextureAtlasBuilder<'a> { texture: &Image, packed_location: &PackedLocation, padding: UVec2, - ) { + ) -> TextureAtlasBuilderResult<()> { let rect_width = (packed_location.width() - padding.x) as usize; let rect_height = (packed_location.height() - padding.y) as usize; let rect_x = packed_location.x() as usize; @@ -113,14 +119,20 @@ impl<'a> TextureAtlasBuilder<'a> { let atlas_width = atlas_texture.width() as usize; let format_size = atlas_texture.texture_descriptor.format.pixel_size(); + let Some(ref mut atlas_data) = atlas_texture.data else { + return Err(TextureAtlasBuilderError::UninitializedAtlas); + }; + let Some(ref data) = texture.data else { + return Err(TextureAtlasBuilderError::UninitializedSourceTexture); + }; for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() { let begin = (bound_y * atlas_width + rect_x) * format_size; let end = begin + rect_width * format_size; let texture_begin = texture_y * rect_width * format_size; let texture_end = texture_begin + rect_width * format_size; - atlas_texture.data[begin..end] - .copy_from_slice(&texture.data[texture_begin..texture_end]); + atlas_data[begin..end].copy_from_slice(&data[texture_begin..texture_end]); } + Ok(()) } fn copy_converted_texture( @@ -128,9 +140,9 @@ impl<'a> TextureAtlasBuilder<'a> { atlas_texture: &mut Image, texture: &Image, packed_location: &PackedLocation, - ) { + ) -> TextureAtlasBuilderResult<()> { if self.format == texture.texture_descriptor.format { - Self::copy_texture_to_atlas(atlas_texture, texture, packed_location, self.padding); + Self::copy_texture_to_atlas(atlas_texture, texture, packed_location, self.padding)?; } else if let Some(converted_texture) = texture.convert(self.format) { debug!( "Converting texture from '{:?}' to '{:?}'", @@ -141,13 +153,14 @@ impl<'a> TextureAtlasBuilder<'a> { &converted_texture, packed_location, self.padding, - ); + )?; } else { error!( "Error converting texture from '{:?}' to '{:?}', ignoring", texture.texture_descriptor.format, self.format ); } + Ok(()) } /// Consumes the builder, and returns the newly created texture atlas and @@ -274,7 +287,7 @@ impl<'a> TextureAtlasBuilder<'a> { ); return Err(TextureAtlasBuilderError::WrongFormat); } - self.copy_converted_texture(&mut atlas_texture, texture, packed_location); + self.copy_converted_texture(&mut atlas_texture, texture, packed_location)?; } Ok(( diff --git a/crates/bevy_input/LICENSE-APACHE b/crates/bevy_input/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_input/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_input/LICENSE-MIT b/crates/bevy_input/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_input/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_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 2da9916a7ed7a..cdacd458f6c28 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1539,7 +1539,7 @@ pub fn gamepad_event_processing_system( match event { // Connections require inserting/removing components so they are done in a separate system RawGamepadEvent::Connection(send_event) => { - processed_events.send(GamepadEvent::from(send_event.clone())); + processed_events.write(GamepadEvent::from(send_event.clone())); } RawGamepadEvent::Axis(RawGamepadAxisChangedEvent { gamepad, @@ -1559,8 +1559,8 @@ pub fn gamepad_event_processing_system( gamepad_axis.analog.set(axis, filtered_value.raw); let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value.scaled.to_f32()); - processed_axis_events.send(send_event); - processed_events.send(GamepadEvent::from(send_event)); + processed_axis_events.write(send_event); + processed_events.write(GamepadEvent::from(send_event)); } RawGamepadEvent::Button(RawGamepadButtonChangedEvent { gamepad, @@ -1583,7 +1583,7 @@ pub fn gamepad_event_processing_system( if button_settings.is_released(filtered_value.raw) { // Check if button was previously pressed if gamepad_buttons.pressed(button) { - processed_digital_events.send(GamepadButtonStateChangedEvent::new( + processed_digital_events.write(GamepadButtonStateChangedEvent::new( gamepad, button, ButtonState::Released, @@ -1595,7 +1595,7 @@ pub fn gamepad_event_processing_system( } else if button_settings.is_pressed(filtered_value.raw) { // Check if button was previously not pressed if !gamepad_buttons.pressed(button) { - processed_digital_events.send(GamepadButtonStateChangedEvent::new( + processed_digital_events.write(GamepadButtonStateChangedEvent::new( gamepad, button, ButtonState::Pressed, @@ -1615,8 +1615,8 @@ pub fn gamepad_event_processing_system( button_state, filtered_value.scaled.to_f32(), ); - processed_analog_events.send(send_event); - processed_events.send(GamepadEvent::from(send_event)); + processed_analog_events.write(send_event); + processed_events.write(GamepadEvent::from(send_event)); } } } @@ -1699,7 +1699,7 @@ impl GamepadRumbleIntensity { /// gamepads: Query>, /// ) { /// for entity in gamepads.iter() { -/// rumble_requests.send(GamepadRumbleRequest::Add { +/// rumble_requests.write(GamepadRumbleRequest::Add { /// gamepad: entity, /// intensity: GamepadRumbleIntensity::MAX, /// duration: Duration::from_secs_f32(0.5), diff --git a/crates/bevy_input_focus/LICENSE-APACHE b/crates/bevy_input_focus/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_input_focus/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_input_focus/LICENSE-MIT b/crates/bevy_input_focus/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_input_focus/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_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 3bdbb019c76e2..6a128c9c38586 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -33,7 +33,7 @@ dds = [ "bevy_image/dds", "bevy_render/dds", "bevy_core_pipeline/dds", - "bevy_gltf/dds", + "bevy_gltf?/dds", ] exr = ["bevy_image/exr", "bevy_render/exr"] hdr = ["bevy_image/hdr", "bevy_render/hdr"] @@ -59,6 +59,10 @@ webp = ["bevy_image/webp"] # Enable SPIR-V passthrough spirv_shader_passthrough = ["bevy_render/spirv_shader_passthrough"] +# Statically linked DXC shader compiler for DirectX 12 +# TODO: When wgpu switches to DirectX 12 instead of Vulkan by default on windows, make this a default feature +statically-linked-dxc = ["bevy_render/statically-linked-dxc"] + # Include tonemapping LUT KTX2 files. tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"] @@ -249,9 +253,6 @@ bevy_ui_picking_backend = ["bevy_picking", "bevy_ui/bevy_ui_picking_backend"] # Provides a UI debug overlay bevy_ui_debug = ["bevy_ui?/bevy_ui_debug"] -# Enable support for the ios_simulator by downgrading some rendering capabilities -ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"] - # Enable built in global state machines bevy_state = ["dep:bevy_state"] @@ -265,6 +266,9 @@ reflect_functions = [ "bevy_ecs/reflect_functions", ] +# Enable documentation reflection +reflect_documentation = ["bevy_reflect/documentation"] + # Enable winit custom cursor support custom_cursor = ["bevy_winit/custom_cursor"] diff --git a/crates/bevy_internal/LICENSE-APACHE b/crates/bevy_internal/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_internal/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_internal/LICENSE-MIT b/crates/bevy_internal/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_internal/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_log/Cargo.toml b/crates/bevy_log/Cargo.toml index 9a982b4209ced..6110734f9b326 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -30,8 +30,8 @@ tracing = { version = "0.1", default-features = false, features = ["std"] } # Tracy dependency compatibility table: # https://github.com/nagisa/rust_tracy_client -tracing-tracy = { version = "0.11.0", optional = true } -tracy-client = { version = "0.17.0", optional = true } +tracing-tracy = { version = "0.11.4", optional = true } +tracy-client = { version = "0.18.0", optional = true } [target.'cfg(target_os = "android")'.dependencies] android_log-sys = "0.3.0" diff --git a/crates/bevy_log/LICENSE-APACHE b/crates/bevy_log/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_log/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_log/LICENSE-MIT b/crates/bevy_log/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_log/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_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index 91f68913f6dd8..4fb0e8291bf64 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -9,12 +9,10 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [dependencies] -toml_edit = { version = "0.22.7", default-features = false, features = [ - "parse", -] } syn = "2.0" quote = "1.0" proc-macro2 = "1.0" +cargo-manifest-proc-macros = "0.3.4" [lints] workspace = true diff --git a/crates/bevy_macro_utils/LICENSE-APACHE b/crates/bevy_macro_utils/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_macro_utils/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_macro_utils/LICENSE-MIT b/crates/bevy_macro_utils/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_macro_utils/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_macro_utils/src/bevy_manifest.rs b/crates/bevy_macro_utils/src/bevy_manifest.rs index 2185f09252d0f..825ff725a2683 100644 --- a/crates/bevy_macro_utils/src/bevy_manifest.rs +++ b/crates/bevy_macro_utils/src/bevy_manifest.rs @@ -1,83 +1,54 @@ extern crate proc_macro; +use std::sync::MutexGuard; + +use cargo_manifest_proc_macros::{ + CargoManifest, CrateReExportingPolicy, KnownReExportingCrate, PathPiece, + TryResolveCratePathError, +}; use proc_macro::TokenStream; -use std::{env, path::PathBuf, sync::LazyLock}; -use toml_edit::{DocumentMut, Item}; -/// The path to the `Cargo.toml` file for the Bevy project. -pub struct BevyManifest { - manifest: DocumentMut, +struct BevyReExportingPolicy; + +impl CrateReExportingPolicy for BevyReExportingPolicy { + fn get_re_exported_crate_path(&self, crate_name: &str) -> Option { + crate_name.strip_prefix("bevy_").map(|s| { + let mut path = PathPiece::new(); + path.push(syn::parse_str::(s).unwrap()); + path + }) + } } const BEVY: &str = "bevy"; -const BEVY_INTERNAL: &str = "bevy_internal"; + +const KNOWN_RE_EXPORTING_CRATE_BEVY: KnownReExportingCrate = KnownReExportingCrate { + re_exporting_crate_package_name: BEVY, + crate_re_exporting_policy: &BevyReExportingPolicy {}, +}; + +const ALL_KNOWN_RE_EXPORTING_CRATES: &[&KnownReExportingCrate] = &[&KNOWN_RE_EXPORTING_CRATE_BEVY]; + +/// The path to the `Cargo.toml` file for the Bevy project. +pub struct BevyManifest(MutexGuard<'static, CargoManifest>); impl BevyManifest { /// Returns a global shared instance of the [`BevyManifest`] struct. - pub fn shared() -> &'static LazyLock { - static LAZY_SELF: LazyLock = LazyLock::new(|| BevyManifest { - manifest: env::var_os("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .map(|mut path| { - path.push("Cargo.toml"); - if !path.exists() { - panic!( - "No Cargo manifest found for crate. Expected: {}", - path.display() - ); - } - let manifest = std::fs::read_to_string(path.clone()).unwrap_or_else(|_| { - panic!("Unable to read cargo manifest: {}", path.display()) - }); - manifest.parse::().unwrap_or_else(|_| { - panic!("Failed to parse cargo manifest: {}", path.display()) - }) - }) - .expect("CARGO_MANIFEST_DIR is not defined."), - }); - &LAZY_SELF + pub fn shared() -> Self { + Self(CargoManifest::shared()) } /// Attempt to retrieve the [path](syn::Path) of a particular package in /// the [manifest](BevyManifest) by [name](str). - pub fn maybe_get_path(&self, name: &str) -> Option { - fn dep_package(dep: &Item) -> Option<&str> { - if dep.as_str().is_some() { - None - } else { - dep.get("package").map(|name| name.as_str().unwrap()) - } - } - - let find_in_deps = |deps: &Item| -> Option { - let package = if let Some(dep) = deps.get(name) { - return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); - } else if let Some(dep) = deps.get(BEVY) { - dep_package(dep).unwrap_or(BEVY) - } else if let Some(dep) = deps.get(BEVY_INTERNAL) { - dep_package(dep).unwrap_or(BEVY_INTERNAL) - } else { - return None; - }; - - let mut path = Self::parse_str::(package); - if let Some(module) = name.strip_prefix("bevy_") { - path.segments.push(Self::parse_str(module)); - } - Some(path) - }; - - let deps = self.manifest.get("dependencies"); - let deps_dev = self.manifest.get("dev-dependencies"); - - deps.and_then(find_in_deps) - .or_else(|| deps_dev.and_then(find_in_deps)) + pub fn maybe_get_path(&self, name: &str) -> Result { + self.0 + .try_resolve_crate_path(name, ALL_KNOWN_RE_EXPORTING_CRATES) } /// Returns the path for the crate with the given name. pub fn get_path(&self, name: &str) -> syn::Path { self.maybe_get_path(name) - .unwrap_or_else(|| Self::parse_str(name)) + .expect("Failed to get path for crate") } /// Attempt to parse the provided [path](str) as a [syntax tree node](syn::parse::Parse) @@ -97,7 +68,7 @@ impl BevyManifest { } /// Attempt to get a subcrate [path](syn::Path) under Bevy by [name](str) - pub fn get_subcrate(&self, subcrate: &str) -> Option { + pub fn get_subcrate(&self, subcrate: &str) -> Result { self.maybe_get_path(BEVY) .map(|bevy_path| { let mut segments = bevy_path.segments; @@ -107,6 +78,6 @@ impl BevyManifest { segments, } }) - .or_else(|| self.maybe_get_path(&format!("bevy_{subcrate}"))) + .or_else(|_err| self.maybe_get_path(&format!("bevy_{subcrate}"))) } } diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 3ef041492a8cf..a4c098d20a649 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -36,9 +36,7 @@ approx = "0.5" rand = "0.8" rand_chacha = "0.3" # Enable the approx feature when testing. -bevy_math = { path = ".", version = "0.16.0-dev", default-features = false, features = [ - "approx", -] } +bevy_math = { path = ".", default-features = false, features = ["approx"] } glam = { version = "0.29", default-features = false, features = ["approx"] } [features] diff --git a/crates/bevy_math/LICENSE-APACHE b/crates/bevy_math/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_math/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_math/LICENSE-MIT b/crates/bevy_math/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_math/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_math/images/easefunction/BackIn.svg b/crates/bevy_math/images/easefunction/BackIn.svg new file mode 100644 index 0000000000000..63a776ea4aaaa --- /dev/null +++ b/crates/bevy_math/images/easefunction/BackIn.svg @@ -0,0 +1,5 @@ + +BackIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BackInOut.svg b/crates/bevy_math/images/easefunction/BackInOut.svg new file mode 100644 index 0000000000000..8695c42f719ae --- /dev/null +++ b/crates/bevy_math/images/easefunction/BackInOut.svg @@ -0,0 +1,5 @@ + +BackInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BackOut.svg b/crates/bevy_math/images/easefunction/BackOut.svg new file mode 100644 index 0000000000000..9479d0311b2f5 --- /dev/null +++ b/crates/bevy_math/images/easefunction/BackOut.svg @@ -0,0 +1,5 @@ + +BackOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BothSteps.svg b/crates/bevy_math/images/easefunction/BothSteps.svg new file mode 100644 index 0000000000000..92090fa5d4116 --- /dev/null +++ b/crates/bevy_math/images/easefunction/BothSteps.svg @@ -0,0 +1,5 @@ + +BothSteps(4, Both) + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BounceIn.svg b/crates/bevy_math/images/easefunction/BounceIn.svg new file mode 100644 index 0000000000000..e5c22a6095a32 --- /dev/null +++ b/crates/bevy_math/images/easefunction/BounceIn.svg @@ -0,0 +1,5 @@ + +BounceIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BounceInOut.svg b/crates/bevy_math/images/easefunction/BounceInOut.svg new file mode 100644 index 0000000000000..9aaf4e1e0aca3 --- /dev/null +++ b/crates/bevy_math/images/easefunction/BounceInOut.svg @@ -0,0 +1,5 @@ + +BounceInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/BounceOut.svg b/crates/bevy_math/images/easefunction/BounceOut.svg new file mode 100644 index 0000000000000..080eb0d198dce --- /dev/null +++ b/crates/bevy_math/images/easefunction/BounceOut.svg @@ -0,0 +1,5 @@ + +BounceOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CircularIn.svg b/crates/bevy_math/images/easefunction/CircularIn.svg new file mode 100644 index 0000000000000..3d8c7e05792a5 --- /dev/null +++ b/crates/bevy_math/images/easefunction/CircularIn.svg @@ -0,0 +1,5 @@ + +CircularIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CircularInOut.svg b/crates/bevy_math/images/easefunction/CircularInOut.svg new file mode 100644 index 0000000000000..de2de720ebde4 --- /dev/null +++ b/crates/bevy_math/images/easefunction/CircularInOut.svg @@ -0,0 +1,5 @@ + +CircularInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CircularOut.svg b/crates/bevy_math/images/easefunction/CircularOut.svg new file mode 100644 index 0000000000000..740a59cd68f83 --- /dev/null +++ b/crates/bevy_math/images/easefunction/CircularOut.svg @@ -0,0 +1,5 @@ + +CircularOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CubicIn.svg b/crates/bevy_math/images/easefunction/CubicIn.svg new file mode 100644 index 0000000000000..f8acbc762a19e --- /dev/null +++ b/crates/bevy_math/images/easefunction/CubicIn.svg @@ -0,0 +1,5 @@ + +CubicIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CubicInOut.svg b/crates/bevy_math/images/easefunction/CubicInOut.svg new file mode 100644 index 0000000000000..3fa5859f94b19 --- /dev/null +++ b/crates/bevy_math/images/easefunction/CubicInOut.svg @@ -0,0 +1,5 @@ + +CubicInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/CubicOut.svg b/crates/bevy_math/images/easefunction/CubicOut.svg new file mode 100644 index 0000000000000..1c4f1abc558b1 --- /dev/null +++ b/crates/bevy_math/images/easefunction/CubicOut.svg @@ -0,0 +1,5 @@ + +CubicOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/Elastic.svg b/crates/bevy_math/images/easefunction/Elastic.svg new file mode 100644 index 0000000000000..fa187328cb65c --- /dev/null +++ b/crates/bevy_math/images/easefunction/Elastic.svg @@ -0,0 +1,5 @@ + +Elastic(50.0) + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ElasticIn.svg b/crates/bevy_math/images/easefunction/ElasticIn.svg new file mode 100644 index 0000000000000..faa95a79a2eb8 --- /dev/null +++ b/crates/bevy_math/images/easefunction/ElasticIn.svg @@ -0,0 +1,5 @@ + +ElasticIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ElasticInOut.svg b/crates/bevy_math/images/easefunction/ElasticInOut.svg new file mode 100644 index 0000000000000..e0f0527a4f2c2 --- /dev/null +++ b/crates/bevy_math/images/easefunction/ElasticInOut.svg @@ -0,0 +1,5 @@ + +ElasticInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ElasticOut.svg b/crates/bevy_math/images/easefunction/ElasticOut.svg new file mode 100644 index 0000000000000..17817a6712a34 --- /dev/null +++ b/crates/bevy_math/images/easefunction/ElasticOut.svg @@ -0,0 +1,5 @@ + +ElasticOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/EndSteps.svg b/crates/bevy_math/images/easefunction/EndSteps.svg new file mode 100644 index 0000000000000..dafe6825fef07 --- /dev/null +++ b/crates/bevy_math/images/easefunction/EndSteps.svg @@ -0,0 +1,5 @@ + +EndSteps(4, End) + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ExponentialIn.svg b/crates/bevy_math/images/easefunction/ExponentialIn.svg new file mode 100644 index 0000000000000..784cc6ccd558f --- /dev/null +++ b/crates/bevy_math/images/easefunction/ExponentialIn.svg @@ -0,0 +1,5 @@ + +ExponentialIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ExponentialInOut.svg b/crates/bevy_math/images/easefunction/ExponentialInOut.svg new file mode 100644 index 0000000000000..3cc55941b090d --- /dev/null +++ b/crates/bevy_math/images/easefunction/ExponentialInOut.svg @@ -0,0 +1,5 @@ + +ExponentialInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/ExponentialOut.svg b/crates/bevy_math/images/easefunction/ExponentialOut.svg new file mode 100644 index 0000000000000..0fb9f8720e4ad --- /dev/null +++ b/crates/bevy_math/images/easefunction/ExponentialOut.svg @@ -0,0 +1,5 @@ + +ExponentialOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/Linear.svg b/crates/bevy_math/images/easefunction/Linear.svg new file mode 100644 index 0000000000000..99adedf3e8275 --- /dev/null +++ b/crates/bevy_math/images/easefunction/Linear.svg @@ -0,0 +1,5 @@ + +Linear + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/NoneSteps.svg b/crates/bevy_math/images/easefunction/NoneSteps.svg new file mode 100644 index 0000000000000..8434f4126b7b3 --- /dev/null +++ b/crates/bevy_math/images/easefunction/NoneSteps.svg @@ -0,0 +1,5 @@ + +NoneSteps(4, None) + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuadraticIn.svg b/crates/bevy_math/images/easefunction/QuadraticIn.svg new file mode 100644 index 0000000000000..15ba4403144b7 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuadraticIn.svg @@ -0,0 +1,5 @@ + +QuadraticIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuadraticInOut.svg b/crates/bevy_math/images/easefunction/QuadraticInOut.svg new file mode 100644 index 0000000000000..f2f1098aaedde --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuadraticInOut.svg @@ -0,0 +1,5 @@ + +QuadraticInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuadraticOut.svg b/crates/bevy_math/images/easefunction/QuadraticOut.svg new file mode 100644 index 0000000000000..5b9d9a1b6c170 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuadraticOut.svg @@ -0,0 +1,5 @@ + +QuadraticOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuarticIn.svg b/crates/bevy_math/images/easefunction/QuarticIn.svg new file mode 100644 index 0000000000000..6028e35542820 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuarticIn.svg @@ -0,0 +1,5 @@ + +QuarticIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuarticInOut.svg b/crates/bevy_math/images/easefunction/QuarticInOut.svg new file mode 100644 index 0000000000000..7afd6c74b4296 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuarticInOut.svg @@ -0,0 +1,5 @@ + +QuarticInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuarticOut.svg b/crates/bevy_math/images/easefunction/QuarticOut.svg new file mode 100644 index 0000000000000..f9fac715350e7 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuarticOut.svg @@ -0,0 +1,5 @@ + +QuarticOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuinticIn.svg b/crates/bevy_math/images/easefunction/QuinticIn.svg new file mode 100644 index 0000000000000..9dfaa926bf5a8 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuinticIn.svg @@ -0,0 +1,5 @@ + +QuinticIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuinticInOut.svg b/crates/bevy_math/images/easefunction/QuinticInOut.svg new file mode 100644 index 0000000000000..732b9db365e91 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuinticInOut.svg @@ -0,0 +1,5 @@ + +QuinticInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/QuinticOut.svg b/crates/bevy_math/images/easefunction/QuinticOut.svg new file mode 100644 index 0000000000000..76da9d2e9f020 --- /dev/null +++ b/crates/bevy_math/images/easefunction/QuinticOut.svg @@ -0,0 +1,5 @@ + +QuinticOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/README.md b/crates/bevy_math/images/easefunction/README.md new file mode 100644 index 0000000000000..e341ea0e14bb3 --- /dev/null +++ b/crates/bevy_math/images/easefunction/README.md @@ -0,0 +1,3 @@ +# EaseFunction + +These graphs are auto-generated via `tools/build-easefunction-graphs`. diff --git a/crates/bevy_math/images/easefunction/SineIn.svg b/crates/bevy_math/images/easefunction/SineIn.svg new file mode 100644 index 0000000000000..e9630ca1d80f1 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SineIn.svg @@ -0,0 +1,5 @@ + +SineIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SineInOut.svg b/crates/bevy_math/images/easefunction/SineInOut.svg new file mode 100644 index 0000000000000..20e251e7da6c7 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SineInOut.svg @@ -0,0 +1,5 @@ + +SineInOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SineOut.svg b/crates/bevy_math/images/easefunction/SineOut.svg new file mode 100644 index 0000000000000..2cf451d7849ef --- /dev/null +++ b/crates/bevy_math/images/easefunction/SineOut.svg @@ -0,0 +1,5 @@ + +SineOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmoothStep.svg b/crates/bevy_math/images/easefunction/SmoothStep.svg new file mode 100644 index 0000000000000..74d8770317457 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmoothStep.svg @@ -0,0 +1,5 @@ + +SmoothStep + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmoothStepIn.svg b/crates/bevy_math/images/easefunction/SmoothStepIn.svg new file mode 100644 index 0000000000000..84363c6448e54 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmoothStepIn.svg @@ -0,0 +1,5 @@ + +SmoothStepIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmoothStepOut.svg b/crates/bevy_math/images/easefunction/SmoothStepOut.svg new file mode 100644 index 0000000000000..09256cbd65533 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmoothStepOut.svg @@ -0,0 +1,5 @@ + +SmoothStepOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmootherStep.svg b/crates/bevy_math/images/easefunction/SmootherStep.svg new file mode 100644 index 0000000000000..9c00abead6f70 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmootherStep.svg @@ -0,0 +1,5 @@ + +SmootherStep + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmootherStepIn.svg b/crates/bevy_math/images/easefunction/SmootherStepIn.svg new file mode 100644 index 0000000000000..5af3150057425 --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmootherStepIn.svg @@ -0,0 +1,5 @@ + +SmootherStepIn + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/SmootherStepOut.svg b/crates/bevy_math/images/easefunction/SmootherStepOut.svg new file mode 100644 index 0000000000000..5c9df92500afa --- /dev/null +++ b/crates/bevy_math/images/easefunction/SmootherStepOut.svg @@ -0,0 +1,5 @@ + +SmootherStepOut + + + \ No newline at end of file diff --git a/crates/bevy_math/images/easefunction/StartSteps.svg b/crates/bevy_math/images/easefunction/StartSteps.svg new file mode 100644 index 0000000000000..476a17d364ad3 --- /dev/null +++ b/crates/bevy_math/images/easefunction/StartSteps.svg @@ -0,0 +1,5 @@ + +StartSteps(4, Start) + + + \ No newline at end of file diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index f7ae1a1b80909..18f42d3f13f24 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -150,8 +150,83 @@ all_tuples_enumerated!( /// /// The resulting curve's domain is always [the unit interval]. /// +/// # Example +/// +/// Create a linear curve that interpolates between `2.0` and `4.0`. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear); +/// ``` +/// +/// [`sample`] the curve at various points. This will return `None` if the parameter +/// is outside the unit interval. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear); +/// assert_eq!(c.sample(-1.0), None); +/// assert_eq!(c.sample(0.0), Some(2.0)); +/// assert_eq!(c.sample(0.5), Some(3.0)); +/// assert_eq!(c.sample(1.0), Some(4.0)); +/// assert_eq!(c.sample(2.0), None); +/// ``` +/// +/// [`sample_clamped`] will clamp the parameter to the unit interval, so it +/// always returns a value. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear); +/// assert_eq!(c.sample_clamped(-1.0), 2.0); +/// assert_eq!(c.sample_clamped(0.0), 2.0); +/// assert_eq!(c.sample_clamped(0.5), 3.0); +/// assert_eq!(c.sample_clamped(1.0), 4.0); +/// assert_eq!(c.sample_clamped(2.0), 4.0); +/// ``` +/// +/// `EasingCurve` can be used with any type that implements the [`Ease`] trait. +/// This includes many math types, like vectors and rotations. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// let c = EasingCurve::new( +/// Vec2::new(0.0, 4.0), +/// Vec2::new(2.0, 8.0), +/// EaseFunction::Linear, +/// ); +/// +/// assert_eq!(c.sample_clamped(0.5), Vec2::new(1.0, 6.0)); +/// ``` +/// +/// ``` +/// # use bevy_math::prelude::*; +/// # use approx::assert_abs_diff_eq; +/// let c = EasingCurve::new( +/// Rot2::degrees(10.0), +/// Rot2::degrees(20.0), +/// EaseFunction::Linear, +/// ); +/// +/// assert_abs_diff_eq!(c.sample_clamped(0.5), Rot2::degrees(15.0)); +/// ``` +/// +/// As a shortcut, an `EasingCurve` between `0.0` and `1.0` can be replaced by +/// [`EaseFunction`]. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// # let t = 0.5; +/// let f = EaseFunction::SineIn; +/// let c = EasingCurve::new(0.0, 1.0, EaseFunction::SineIn); +/// +/// assert_eq!(f.sample(t), c.sample(t)); +/// ``` +/// /// [easing function]: EaseFunction /// [the unit interval]: Interval::UNIT +/// [`sample`]: EasingCurve::sample +/// [`sample_clamped`]: EasingCurve::sample_clamped #[derive(Clone, Debug)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] @@ -194,15 +269,99 @@ where } } +/// Configuration options for the [`EaseFunction::Steps`] curves. This closely replicates the +/// [CSS step function specification]. +/// +/// [CSS step function specification]: https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/steps#description +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +pub enum JumpAt { + /// Indicates that the first step happens when the animation begins. + /// + #[doc = include_str!("../../images/easefunction/StartSteps.svg")] + Start, + /// Indicates that the last step happens when the animation ends. + /// + #[doc = include_str!("../../images/easefunction/EndSteps.svg")] + #[default] + End, + /// Indicates neither early nor late jumps happen. + /// + #[doc = include_str!("../../images/easefunction/NoneSteps.svg")] + None, + /// Indicates both early and late jumps happen. + /// + #[doc = include_str!("../../images/easefunction/BothSteps.svg")] + Both, +} + +impl JumpAt { + #[inline] + pub(crate) fn eval(self, num_steps: usize, t: f32) -> f32 { + use crate::ops; + + let (a, b) = match self { + JumpAt::Start => (1.0, 0), + JumpAt::End => (0.0, 0), + JumpAt::None => (0.0, -1), + JumpAt::Both => (1.0, 1), + }; + + let current_step = ops::floor(t * num_steps as f32) + a; + let step_size = (num_steps as isize + b).max(1) as f32; + + (current_step / step_size).clamp(0.0, 1.0) + } +} + /// Curve functions over the [unit interval], commonly used for easing transitions. /// +/// `EaseFunction` can be used on its own to interpolate between `0.0` and `1.0`. +/// It can also be combined with [`EasingCurve`] to interpolate between other +/// intervals and types, including vectors and rotations. +/// +/// # Example +/// +/// [`sample`] the smoothstep function at various points. This will return `None` +/// if the parameter is outside the unit interval. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// let f = EaseFunction::SmoothStep; +/// +/// assert_eq!(f.sample(-1.0), None); +/// assert_eq!(f.sample(0.0), Some(0.0)); +/// assert_eq!(f.sample(0.5), Some(0.5)); +/// assert_eq!(f.sample(1.0), Some(1.0)); +/// assert_eq!(f.sample(2.0), None); +/// ``` +/// +/// [`sample_clamped`] will clamp the parameter to the unit interval, so it +/// always returns a value. +/// +/// ``` +/// # use bevy_math::prelude::*; +/// # let f = EaseFunction::SmoothStep; +/// assert_eq!(f.sample_clamped(-1.0), 0.0); +/// assert_eq!(f.sample_clamped(0.0), 0.0); +/// assert_eq!(f.sample_clamped(0.5), 0.5); +/// assert_eq!(f.sample_clamped(1.0), 1.0); +/// assert_eq!(f.sample_clamped(2.0), 1.0); +/// ``` +/// +/// [`sample`]: EaseFunction::sample +/// [`sample_clamped`]: EaseFunction::sample_clamped /// [unit interval]: `Interval::UNIT` #[non_exhaustive] #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +// Note: Graphs are auto-generated via `tools/build-easefunction-graphs`. pub enum EaseFunction { /// `f(t) = t` + /// + #[doc = include_str!("../../images/easefunction/Linear.svg")] Linear, /// `f(t) = t²` @@ -211,6 +370,8 @@ pub enum EaseFunction { /// - f(0) = 0 /// - f(1) = 1 /// - f′(0) = 0 + /// + #[doc = include_str!("../../images/easefunction/QuadraticIn.svg")] QuadraticIn, /// `f(t) = -(t * (t - 2.0))` /// @@ -218,12 +379,16 @@ pub enum EaseFunction { /// - f(0) = 0 /// - f(1) = 1 /// - f′(1) = 0 + /// + #[doc = include_str!("../../images/easefunction/QuadraticOut.svg")] QuadraticOut, /// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5 /// /// A quadratic has too low of a degree to be both an `InOut` and C², /// so consider using at least a cubic (such as [`EaseFunction::SmoothStep`]) /// if you want the acceleration to be continuous. + /// + #[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")] QuadraticInOut, /// `f(t) = t³` @@ -233,8 +398,12 @@ pub enum EaseFunction { /// - f(1) = 1 /// - f′(0) = 0 /// - f″(0) = 0 + /// + #[doc = include_str!("../../images/easefunction/CubicIn.svg")] CubicIn, /// `f(t) = (t - 1.0)³ + 1.0` + /// + #[doc = include_str!("../../images/easefunction/CubicOut.svg")] CubicOut, /// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5 /// @@ -244,18 +413,30 @@ pub enum EaseFunction { /// Consider using [`EaseFunction::SmoothStep`] instead, which is also cubic, /// or [`EaseFunction::SmootherStep`] if you picked this because you wanted /// the acceleration at the endpoints to also be zero. + /// + #[doc = include_str!("../../images/easefunction/CubicInOut.svg")] CubicInOut, /// `f(t) = t⁴` + /// + #[doc = include_str!("../../images/easefunction/QuarticIn.svg")] QuarticIn, /// `f(t) = (t - 1.0)³ * (1.0 - t) + 1.0` + /// + #[doc = include_str!("../../images/easefunction/QuarticOut.svg")] QuarticOut, /// Behaves as `EaseFunction::QuarticIn` for t < 0.5 and as `EaseFunction::QuarticOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/QuarticInOut.svg")] QuarticInOut, /// `f(t) = t⁵` + /// + #[doc = include_str!("../../images/easefunction/QuinticIn.svg")] QuinticIn, /// `f(t) = (t - 1.0)⁵ + 1.0` + /// + #[doc = include_str!("../../images/easefunction/QuinticOut.svg")] QuinticOut, /// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5 /// @@ -263,15 +444,21 @@ pub enum EaseFunction { /// the acceleration jumps from +40 to -40 at t = ½. /// /// Consider using [`EaseFunction::SmootherStep`] instead, which is also quintic. + /// + #[doc = include_str!("../../images/easefunction/QuinticInOut.svg")] QuinticInOut, /// Behaves as the first half of [`EaseFunction::SmoothStep`]. /// /// This has f″(1) = 0, unlike [`EaseFunction::QuadraticIn`] which starts similarly. + /// + #[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")] SmoothStepIn, /// Behaves as the second half of [`EaseFunction::SmoothStep`]. /// /// This has f″(0) = 0, unlike [`EaseFunction::QuadraticOut`] which ends similarly. + /// + #[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")] SmoothStepOut, /// `f(t) = 2t³ + 3t²` /// @@ -284,15 +471,21 @@ pub enum EaseFunction { /// See also [`smoothstep` in GLSL][glss]. /// /// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml + /// + #[doc = include_str!("../../images/easefunction/SmoothStep.svg")] SmoothStep, /// Behaves as the first half of [`EaseFunction::SmootherStep`]. /// /// This has f″(1) = 0, unlike [`EaseFunction::CubicIn`] which starts similarly. + /// + #[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")] SmootherStepIn, /// Behaves as the second half of [`EaseFunction::SmootherStep`]. /// /// This has f″(0) = 0, unlike [`EaseFunction::CubicOut`] which ends similarly. + /// + #[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")] SmootherStepOut, /// `f(t) = 6t⁵ - 15t⁴ + 10t³` /// @@ -303,60 +496,101 @@ pub enum EaseFunction { /// - f′(1) = 0 /// - f″(0) = 0 /// - f″(1) = 0 + /// + #[doc = include_str!("../../images/easefunction/SmootherStep.svg")] SmootherStep, /// `f(t) = 1.0 - cos(t * π / 2.0)` + /// + #[doc = include_str!("../../images/easefunction/SineIn.svg")] SineIn, /// `f(t) = sin(t * π / 2.0)` + /// + #[doc = include_str!("../../images/easefunction/SineOut.svg")] SineOut, /// Behaves as `EaseFunction::SineIn` for t < 0.5 and as `EaseFunction::SineOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/SineInOut.svg")] SineInOut, /// `f(t) = 1.0 - sqrt(1.0 - t²)` + /// + #[doc = include_str!("../../images/easefunction/CircularIn.svg")] CircularIn, /// `f(t) = sqrt((2.0 - t) * t)` + /// + #[doc = include_str!("../../images/easefunction/CircularOut.svg")] CircularOut, /// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/CircularInOut.svg")] CircularInOut, /// `f(t) ≈ 2.0^(10.0 * (t - 1.0))` /// /// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`: /// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1). + /// + #[doc = include_str!("../../images/easefunction/ExponentialIn.svg")] ExponentialIn, /// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)` /// /// As with `EaseFunction::ExponentialIn`, the precise definition adjusts it slightly // so it hits both `(0, 0)` and `(1, 1)`. + /// + #[doc = include_str!("../../images/easefunction/ExponentialOut.svg")] ExponentialOut, /// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")] ExponentialInOut, /// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)` + /// + #[doc = include_str!("../../images/easefunction/ElasticIn.svg")] ElasticIn, /// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0` + /// + #[doc = include_str!("../../images/easefunction/ElasticOut.svg")] ElasticOut, /// Behaves as `EaseFunction::ElasticIn` for t < 0.5 and as `EaseFunction::ElasticOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/ElasticInOut.svg")] ElasticInOut, /// `f(t) = 2.70158 * t³ - 1.70158 * t²` + /// + #[doc = include_str!("../../images/easefunction/BackIn.svg")] BackIn, /// `f(t) = 1.0 + 2.70158 * (t - 1.0)³ - 1.70158 * (t - 1.0)²` + /// + #[doc = include_str!("../../images/easefunction/BackOut.svg")] BackOut, /// Behaves as `EaseFunction::BackIn` for t < 0.5 and as `EaseFunction::BackOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/BackInOut.svg")] BackInOut, /// bouncy at the start! + /// + #[doc = include_str!("../../images/easefunction/BounceIn.svg")] BounceIn, /// bouncy at the end! + /// + #[doc = include_str!("../../images/easefunction/BounceOut.svg")] BounceOut, /// Behaves as `EaseFunction::BounceIn` for t < 0.5 and as `EaseFunction::BounceOut` for t >= 0.5 + /// + #[doc = include_str!("../../images/easefunction/BounceInOut.svg")] BounceInOut, - /// `n` steps connecting the start and the end - Steps(usize), + /// `n` steps connecting the start and the end. Jumping behavior is customizable via + /// [`JumpAt`]. See [`JumpAt`] for all the options and visual examples. + Steps(usize, JumpAt), /// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega` + /// + #[doc = include_str!("../../images/easefunction/Elastic.svg")] Elastic(f32), } @@ -605,8 +839,8 @@ mod easing_functions { } #[inline] - pub(crate) fn steps(num_steps: usize, t: f32) -> f32 { - ops::round(t * num_steps as f32) / num_steps.max(1) as f32 + pub(crate) fn steps(num_steps: usize, jump_at: super::JumpAt, t: f32) -> f32 { + jump_at.eval(num_steps, t) } #[inline] @@ -655,14 +889,30 @@ impl EaseFunction { EaseFunction::BounceIn => easing_functions::bounce_in(t), EaseFunction::BounceOut => easing_functions::bounce_out(t), EaseFunction::BounceInOut => easing_functions::bounce_in_out(t), - EaseFunction::Steps(num_steps) => easing_functions::steps(*num_steps, t), + EaseFunction::Steps(num_steps, jump_at) => { + easing_functions::steps(*num_steps, *jump_at, t) + } EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t), } } } +impl Curve for EaseFunction { + #[inline] + fn domain(&self) -> Interval { + Interval::UNIT + } + + #[inline] + fn sample_unchecked(&self, t: f32) -> f32 { + self.eval(t) + } +} + #[cfg(test)] +#[cfg(feature = "approx")] mod tests { + use crate::{Vec2, Vec3, Vec3A}; use approx::assert_abs_diff_eq; @@ -824,4 +1074,118 @@ mod tests { ); }); } + + #[test] + fn jump_at_start() { + let jump_at = JumpAt::Start; + let num_steps = 4; + + [ + (0.0, 0.25), + (0.249, 0.25), + (0.25, 0.5), + (0.499, 0.5), + (0.5, 0.75), + (0.749, 0.75), + (0.75, 1.0), + (1.0, 1.0), + ] + .into_iter() + .for_each(|(t, expected)| { + assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected); + }); + } + + #[test] + fn jump_at_end() { + let jump_at = JumpAt::End; + let num_steps = 4; + + [ + (0.0, 0.0), + (0.249, 0.0), + (0.25, 0.25), + (0.499, 0.25), + (0.5, 0.5), + (0.749, 0.5), + (0.75, 0.75), + (0.999, 0.75), + (1.0, 1.0), + ] + .into_iter() + .for_each(|(t, expected)| { + assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected); + }); + } + + #[test] + fn jump_at_none() { + let jump_at = JumpAt::None; + let num_steps = 5; + + [ + (0.0, 0.0), + (0.199, 0.0), + (0.2, 0.25), + (0.399, 0.25), + (0.4, 0.5), + (0.599, 0.5), + (0.6, 0.75), + (0.799, 0.75), + (0.8, 1.0), + (0.999, 1.0), + (1.0, 1.0), + ] + .into_iter() + .for_each(|(t, expected)| { + assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected); + }); + } + + #[test] + fn jump_at_both() { + let jump_at = JumpAt::Both; + let num_steps = 4; + + [ + (0.0, 0.2), + (0.249, 0.2), + (0.25, 0.4), + (0.499, 0.4), + (0.5, 0.6), + (0.749, 0.6), + (0.75, 0.8), + (0.999, 0.8), + (1.0, 1.0), + ] + .into_iter() + .for_each(|(t, expected)| { + assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected); + }); + } + + #[test] + fn ease_function_curve() { + // Test that using `EaseFunction` directly is equivalent to `EasingCurve::new(0.0, 1.0, ...)`. + + let f = EaseFunction::SmoothStep; + let c = EasingCurve::new(0.0, 1.0, EaseFunction::SmoothStep); + + assert_eq!(f.domain(), c.domain()); + + [ + -1.0, + 0.0, + 0.5, + 1.0, + 2.0, + -f32::MIN_POSITIVE, + 1.0 + f32::EPSILON, + ] + .into_iter() + .for_each(|t| { + assert_eq!(f.sample(t), c.sample(t)); + assert_eq!(f.sample_clamped(t), c.sample_clamped(t)); + }); + } } diff --git a/crates/bevy_math/src/curve/mod.rs b/crates/bevy_math/src/curve/mod.rs index 659f4fee686c5..94e7b0151e226 100644 --- a/crates/bevy_math/src/curve/mod.rs +++ b/crates/bevy_math/src/curve/mod.rs @@ -1061,17 +1061,15 @@ mod tests { let start = Vec2::ZERO; let end = Vec2::new(1.0, 2.0); - let curve = EasingCurve::new(start, end, EaseFunction::Steps(4)); + let curve = EasingCurve::new(start, end, EaseFunction::Steps(4, JumpAt::End)); [ (0.0, start), - (0.124, start), - (0.125, Vec2::new(0.25, 0.5)), - (0.374, Vec2::new(0.25, 0.5)), - (0.375, Vec2::new(0.5, 1.0)), - (0.624, Vec2::new(0.5, 1.0)), - (0.625, Vec2::new(0.75, 1.5)), - (0.874, Vec2::new(0.75, 1.5)), - (0.875, end), + (0.249, start), + (0.250, Vec2::new(0.25, 0.5)), + (0.499, Vec2::new(0.25, 0.5)), + (0.500, Vec2::new(0.5, 1.0)), + (0.749, Vec2::new(0.5, 1.0)), + (0.750, Vec2::new(0.75, 1.5)), (1.0, end), ] .into_iter() diff --git a/crates/bevy_math/src/curve/sample_curves.rs b/crates/bevy_math/src/curve/sample_curves.rs index 681500328b2da..f0fa928abba57 100644 --- a/crates/bevy_math/src/curve/sample_curves.rs +++ b/crates/bevy_math/src/curve/sample_curves.rs @@ -4,6 +4,7 @@ use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError}; use super::{Curve, Interval}; use crate::StableInterpolate; +#[cfg(feature = "bevy_reflect")] use alloc::format; use core::any::type_name; use core::fmt::{self, Debug}; diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 3e32d782babe4..e61e84ee1a129 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -198,9 +198,11 @@ impl Dir2 { /// let dir2 = Dir2::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(result1, Dir2::from_xy(0.75_f32.sqrt(), 0.5).unwrap()); /// /// let result2 = dir1.slerp(dir2, 0.5); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(result2, Dir2::from_xy(0.5_f32.sqrt(), 0.5_f32.sqrt()).unwrap()); /// ``` #[inline] @@ -457,6 +459,7 @@ impl Dir3 { /// let dir2 = Dir3::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); + /// #[cfg(feature = "approx")] /// assert_relative_eq!( /// result1, /// Dir3::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(), @@ -464,6 +467,7 @@ impl Dir3 { /// ); /// /// let result2 = dir1.slerp(dir2, 0.5); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(result2, Dir3::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap()); /// ``` #[inline] @@ -716,6 +720,7 @@ impl Dir3A { /// let dir2 = Dir3A::Y; /// /// let result1 = dir1.slerp(dir2, 1.0 / 3.0); + /// #[cfg(feature = "approx")] /// assert_relative_eq!( /// result1, /// Dir3A::from_xyz(0.75_f32.sqrt(), 0.5, 0.0).unwrap(), @@ -723,6 +728,7 @@ impl Dir3A { /// ); /// /// let result2 = dir1.slerp(dir2, 0.5); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(result2, Dir3A::from_xyz(0.5_f32.sqrt(), 0.5_f32.sqrt(), 0.0).unwrap()); /// ``` #[inline] @@ -850,6 +856,7 @@ impl approx::UlpsEq for Dir3A { } #[cfg(test)] +#[cfg(feature = "approx")] mod tests { use crate::ops; diff --git a/crates/bevy_math/src/isometry.rs b/crates/bevy_math/src/isometry.rs index e01a8cd713661..1d88736d3bd2e 100644 --- a/crates/bevy_math/src/isometry.rs +++ b/crates/bevy_math/src/isometry.rs @@ -589,6 +589,7 @@ impl UlpsEq for Isometry3d { } #[cfg(test)] +#[cfg(feature = "approx")] mod tests { use super::*; use crate::{vec2, vec3, vec3a}; diff --git a/crates/bevy_math/src/rotation2d.rs b/crates/bevy_math/src/rotation2d.rs index 40760dcb84273..3f5d3031cf1a9 100644 --- a/crates/bevy_math/src/rotation2d.rs +++ b/crates/bevy_math/src/rotation2d.rs @@ -30,9 +30,11 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// assert_eq!(rotation2.as_radians(), PI / 4.0); /// /// // "Add" rotations together using `*` +/// #[cfg(feature = "approx")] /// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0)); /// /// // Rotate vectors +/// #[cfg(feature = "approx")] /// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] @@ -116,9 +118,11 @@ impl Rot2 { /// /// let rot1 = Rot2::radians(3.0 * FRAC_PI_2); /// let rot2 = Rot2::radians(-FRAC_PI_2); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1, rot2); /// /// let rot3 = Rot2::radians(PI); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1 * rot1, rot3); /// ``` #[inline] @@ -141,9 +145,11 @@ impl Rot2 { /// /// let rot1 = Rot2::degrees(270.0); /// let rot2 = Rot2::degrees(-90.0); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1, rot2); /// /// let rot3 = Rot2::degrees(180.0); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1 * rot1, rot3); /// ``` #[inline] @@ -165,9 +171,11 @@ impl Rot2 { /// /// let rot1 = Rot2::turn_fraction(0.75); /// let rot2 = Rot2::turn_fraction(-0.25); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1, rot2); /// /// let rot3 = Rot2::turn_fraction(0.5); + /// #[cfg(feature = "approx")] /// assert_relative_eq!(rot1 * rot1, rot3); /// ``` #[inline] diff --git a/crates/bevy_mesh/Cargo.toml b/crates/bevy_mesh/Cargo.toml index bb98018ab86f2..ffcc2f72e2969 100644 --- a/crates/bevy_mesh/Cargo.toml +++ b/crates/bevy_mesh/Cargo.toml @@ -29,7 +29,7 @@ bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-d # other bitflags = { version = "2.3", features = ["serde"] } bytemuck = { version = "1.5" } -wgpu-types = { version = "23", default-features = false } +wgpu-types = { version = "24", default-features = false } serde = { version = "1", features = ["derive"] } hexasphere = "15.0" thiserror = { version = "2", default-features = false } diff --git a/crates/bevy_mesh/LICENSE-APACHE b/crates/bevy_mesh/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_mesh/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_mesh/LICENSE-MIT b/crates/bevy_mesh/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_mesh/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_mesh/src/mesh.rs b/crates/bevy_mesh/src/mesh.rs index ec36399fadb1b..a07924df2d021 100644 --- a/crates/bevy_mesh/src/mesh.rs +++ b/crates/bevy_mesh/src/mesh.rs @@ -6,7 +6,6 @@ use super::{ GenerateTangentsError, Indices, MeshAttributeData, MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, MeshWindingInvertError, VertexAttributeValues, VertexBufferLayout, - VertexFormatSize, }; use alloc::collections::BTreeMap; use bevy_asset::{Asset, Handle, RenderAssetUsages}; @@ -379,7 +378,7 @@ impl Mesh { pub fn get_vertex_size(&self) -> u64 { self.attributes .values() - .map(|data| data.attribute.format.get_size()) + .map(|data| data.attribute.format.size()) .sum() } @@ -414,7 +413,7 @@ impl Mesh { format: data.attribute.format, shader_location: index as u32, }); - accumulated_offset += data.attribute.format.get_size(); + accumulated_offset += data.attribute.format.size(); } let layout = MeshVertexBufferLayout { @@ -482,7 +481,7 @@ impl Mesh { // bundle into interleaved buffers let mut attribute_offset = 0; for attribute_data in self.attributes.values() { - let attribute_size = attribute_data.attribute.format.get_size() as usize; + let attribute_size = attribute_data.attribute.format.size() as usize; let attributes_bytes = attribute_data.values.get_bytes(); for (vertex_index, attribute_bytes) in attributes_bytes .chunks_exact(attribute_size) diff --git a/crates/bevy_mesh/src/skinning.rs b/crates/bevy_mesh/src/skinning.rs index faf4e8be3ea97..a35ac36224985 100644 --- a/crates/bevy_mesh/src/skinning.rs +++ b/crates/bevy_mesh/src/skinning.rs @@ -1,29 +1,27 @@ -use bevy_asset::{Asset, Handle}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - prelude::ReflectComponent, - reflect::{ReflectMapEntities, ReflectVisitEntities, ReflectVisitEntitiesMut}, -}; +use bevy_asset::{AsAssetId, Asset, AssetId, Handle}; +use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; use bevy_math::Mat4; use bevy_reflect::prelude::*; use core::ops::Deref; -#[derive(Component, Debug, Default, Clone, Reflect, VisitEntities, VisitEntitiesMut)] -#[reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - Default, - Debug -)] +#[derive(Component, Debug, Default, Clone, Reflect)] +#[reflect(Component, Default, Debug)] pub struct SkinnedMesh { - #[visit_entities(ignore)] pub inverse_bindposes: Handle, + #[entities] pub joints: Vec, } +impl AsAssetId for SkinnedMesh { + type Asset = SkinnedMeshInverseBindposes; + + // We implement this so that `AssetChanged` will work to pick up any changes + // to `SkinnedMeshInverseBindposes`. + fn as_asset_id(&self) -> AssetId { + self.inverse_bindposes.id() + } +} + #[derive(Asset, TypePath, Debug)] pub struct SkinnedMeshInverseBindposes(Box<[Mat4]>); diff --git a/crates/bevy_mesh/src/vertex.rs b/crates/bevy_mesh/src/vertex.rs index 51d44f67bae15..253c04af4511c 100644 --- a/crates/bevy_mesh/src/vertex.rs +++ b/crates/bevy_mesh/src/vertex.rs @@ -165,51 +165,6 @@ pub fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { (b - a).cross(c - a).normalize().into() } -pub trait VertexFormatSize { - fn get_size(self) -> u64; -} - -impl VertexFormatSize for VertexFormat { - fn get_size(self) -> u64 { - use core::mem::size_of; - let size = match self { - VertexFormat::Uint8x2 | VertexFormat::Unorm8x2 => size_of::() * 2, - VertexFormat::Uint8x4 | VertexFormat::Unorm8x4 => size_of::() * 4, - VertexFormat::Sint8x2 | VertexFormat::Snorm8x2 => size_of::() * 2, - VertexFormat::Sint8x4 | VertexFormat::Snorm8x4 => size_of::() * 4, - VertexFormat::Unorm10_10_10_2 => 10 + 10 + 10 + 2, - VertexFormat::Uint16x2 | VertexFormat::Unorm16x2 => size_of::() * 2, - VertexFormat::Uint16x4 | VertexFormat::Unorm16x4 => size_of::() * 4, - VertexFormat::Sint16x2 | VertexFormat::Snorm16x2 => size_of::() * 2, - VertexFormat::Sint16x4 | VertexFormat::Snorm16x4 => size_of::() * 4, - // NOTE: As of the time of writing this code, `f16` is not a stabilized primitive, so we - // can't use `size_of::()` here. - VertexFormat::Float16x2 => 2 * 2, - VertexFormat::Float16x4 => 2 * 4, - VertexFormat::Float32 => size_of::(), - VertexFormat::Float32x2 => size_of::() * 2, - VertexFormat::Float32x3 => size_of::() * 3, - VertexFormat::Float32x4 => size_of::() * 4, - VertexFormat::Uint32 => size_of::(), - VertexFormat::Uint32x2 => size_of::() * 2, - VertexFormat::Uint32x3 => size_of::() * 3, - VertexFormat::Uint32x4 => size_of::() * 4, - VertexFormat::Sint32 => size_of::(), - VertexFormat::Sint32x2 => size_of::() * 2, - VertexFormat::Sint32x3 => size_of::() * 3, - VertexFormat::Sint32x4 => size_of::() * 4, - VertexFormat::Float64 => size_of::(), - VertexFormat::Float64x2 => size_of::() * 2, - VertexFormat::Float64x3 => size_of::() * 3, - VertexFormat::Float64x4 => size_of::() * 4, - }; - - // We can safely cast `size` (a `usize`) into a `u64`, as we don't even reach the limits of - // of a `u8`. - size.try_into().unwrap() - } -} - /// Contains an array where each entry describes a property of a single vertex. /// Matches the [`VertexFormats`](VertexFormat). #[derive(Clone, Debug, EnumVariantMeta)] diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index a35e1d205f16a..8e6a2b27b398b 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -136,7 +136,7 @@ pub struct SGroup { pub iNrFaces: i32, pub pFaceIndices: *mut i32, pub iVertexRepresentative: i32, - pub bOrientPreservering: bool, + pub bOrientPreserving: bool, } impl SGroup { @@ -145,7 +145,7 @@ impl SGroup { iNrFaces: 0, pFaceIndices: null_mut(), iVertexRepresentative: 0, - bOrientPreservering: false, + bOrientPreserving: false, } } } @@ -576,11 +576,11 @@ unsafe fn GenerateTSpaces( if (*pTS_out).iCounter == 1i32 { *pTS_out = AvgTSpace(pTS_out, &mut pSubGroupTspace[l]); (*pTS_out).iCounter = 2i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreservering + (*pTS_out).bOrient = (*pGroup).bOrientPreserving } else { *pTS_out = pSubGroupTspace[l]; (*pTS_out).iCounter = 1i32; - (*pTS_out).bOrient = (*pGroup).bOrientPreservering + (*pTS_out).bOrient = (*pGroup).bOrientPreserving } i += 1 } @@ -838,7 +838,7 @@ unsafe fn Build4RuleGroups( *fresh2 = ptr::from_mut(&mut *pGroups.offset(iNrActiveGroups as isize)); (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]) .iVertexRepresentative = vert_index; - (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).bOrientPreservering = + (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).bOrientPreserving = (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32; (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces = 0i32; let ref mut fresh3 = @@ -927,7 +927,7 @@ unsafe fn AssignRecur( && (*pMyTriInfo).AssignedGroup[2usize].is_null() { (*pMyTriInfo).iFlag &= !8i32; - (*pMyTriInfo).iFlag |= if (*pGroup).bOrientPreservering { + (*pMyTriInfo).iFlag |= if (*pGroup).bOrientPreserving { 8i32 } else { 0i32 @@ -939,7 +939,7 @@ unsafe fn AssignRecur( } else { false }; - if bOrient != (*pGroup).bOrientPreservering { + if bOrient != (*pGroup).bOrientPreserving { return false; } AddTriToGroup(pGroup, iMyTriIndex); diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index bf568885e7fef..0b97ceedb9245 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -18,7 +18,6 @@ experimental_pbr_pcss = [] pbr_specular_textures = [] shader_format_glsl = ["bevy_render/shader_format_glsl"] trace = ["bevy_render/trace"] -ios_simulator = ["bevy_render/ios_simulator"] # Enables the meshlet renderer for dense high-poly scenes (experimental) meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"] # Enables processing meshes into meshlet meshes @@ -37,6 +36,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" } bevy_color = { path = "../bevy_color", version = "0.16.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" } +bevy_diagnostic = { path = "../bevy_diagnostic", 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" } @@ -74,6 +74,7 @@ smallvec = "1.6" nonmax = "0.5" static_assertions = "1" tracing = { version = "0.1", default-features = false, features = ["std"] } +offset-allocator = "0.2" [lints] workspace = true diff --git a/crates/bevy_pbr/LICENSE-APACHE b/crates/bevy_pbr/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_pbr/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_pbr/LICENSE-MIT b/crates/bevy_pbr/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_pbr/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_pbr/src/atmosphere/aerial_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl index c353298458c60..f7ba0ecb60cdc 100644 --- a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl @@ -55,12 +55,9 @@ fn main(@builtin(global_invocation_id) idx: vec3) { break; } } - // We only have one channel to store transmittance, so we store the mean - let mean_transmittance = (throughput.r + throughput.g + throughput.b) / 3.0; // Store in log space to allow linear interpolation of exponential values between slices - let log_transmittance = -log(max(mean_transmittance, 1e-6)); // Avoid log(0) let log_inscattering = log(max(total_inscattering, vec3(1e-6))); - textureStore(aerial_view_lut_out, vec3(vec2(idx.xy), slice_i), vec4(log_inscattering, log_transmittance)); + textureStore(aerial_view_lut_out, vec3(vec2(idx.xy), slice_i), vec4(log_inscattering, 0.0)); } } diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index ffe3859b3f781..17a46e18d0b41 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -118,6 +118,27 @@ fn sample_transmittance_lut(r: f32, mu: f32) -> vec3 { return textureSampleLevel(transmittance_lut, transmittance_lut_sampler, uv, 0.0).rgb; } +// NOTICE: This function is copyrighted by Eric Bruneton and INRIA, and falls +// under the license reproduced in bruneton_functions.wgsl (variant of MIT license) +// +// FIXME: this function should be in bruneton_functions.wgsl, but because naga_oil doesn't +// support cyclic imports it's stuck here +fn sample_transmittance_lut_segment(r: f32, mu: f32, t: f32) -> vec3 { + let r_t = get_local_r(r, mu, t); + let mu_t = clamp((r * mu + t) / r_t, -1.0, 1.0); + + if ray_intersects_ground(r, mu) { + return min( + sample_transmittance_lut(r_t, -mu_t) / sample_transmittance_lut(r, -mu), + vec3(1.0) + ); + } else { + return min( + sample_transmittance_lut(r, mu) / sample_transmittance_lut(r_t, mu_t), vec3(1.0) + ); + } +} + fn sample_multiscattering_lut(r: f32, mu: f32) -> vec3 { let uv = multiscattering_lut_r_mu_to_uv(r, mu); return textureSampleLevel(multiscattering_lut, multiscattering_lut_sampler, uv, 0.0).rgb; @@ -130,23 +151,31 @@ fn sample_sky_view_lut(r: f32, ray_dir_as: vec3) -> vec3 { return textureSampleLevel(sky_view_lut, sky_view_lut_sampler, uv, 0.0).rgb; } +fn ndc_to_camera_dist(ndc: vec3) -> f32 { + let view_pos = view.view_from_clip * vec4(ndc, 1.0); + let t = length(view_pos.xyz / view_pos.w) * settings.scene_units_to_m; + return t; +} + // RGB channels: total inscattered light along the camera ray to the current sample. // A channel: average transmittance across all wavelengths to the current sample. -fn sample_aerial_view_lut(uv: vec2, depth: f32) -> vec4 { - let view_pos = view.view_from_clip * vec4(uv_to_ndc(uv), depth, 1.0); - let dist = length(view_pos.xyz / view_pos.w) * settings.scene_units_to_m; +fn sample_aerial_view_lut(uv: vec2, t: f32) -> vec3 { let t_max = settings.aerial_view_lut_max_distance; let num_slices = f32(settings.aerial_view_lut_size.z); - // Offset the W coordinate by -0.5 over the max distance in order to - // align sampling position with slice boundaries, since each texel - // stores the integral over its entire slice - let uvw = vec3(uv, saturate(dist / t_max - 0.5 / num_slices)); + // Each texel stores the value of the scattering integral over the whole slice, + // which requires us to offset the w coordinate by half a slice. For + // example, if we wanted the value of the integral at the boundary between slices, + // we'd need to sample at the center of the previous slice, and vice-versa for + // sampling in the center of a slice. + let uvw = vec3(uv, saturate(t / t_max - 0.5 / num_slices)); let sample = textureSampleLevel(aerial_view_lut, aerial_view_lut_sampler, uvw, 0.0); - // Treat the first slice specially since there is 0 scattering at the camera - let delta_slice = t_max / num_slices; - let fade = saturate(dist / delta_slice); + // Since sampling anywhere between w=0 and w=t_slice will clamp to the first slice, + // we need to do a linear step over the first slice towards zero at the camera's + // position to recover the correct integral value. + let t_slice = t_max / num_slices; + let fade = saturate(t / t_slice); // Recover the values from log space - return exp(sample) * fade; + return exp(sample.rgb) * fade; } // PHASE FUNCTIONS diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index ae110b22429a8..f525c0e2b8d3d 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -46,6 +46,8 @@ use bevy_reflect::Reflect; use bevy_render::{ extract_component::UniformComponentPlugin, render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines}, + renderer::RenderDevice, + settings::WgpuFeatures, }; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -71,26 +73,23 @@ use self::{ }; mod shaders { - use bevy_asset::Handle; + use bevy_asset::{weak_handle, Handle}; use bevy_render::render_resource::Shader; - pub const TYPES: Handle = Handle::weak_from_u128(0xB4CA686B10FA592B508580CCC2F9558C); - pub const FUNCTIONS: Handle = - Handle::weak_from_u128(0xD5524FD88BDC153FBF256B7F2C21906F); + 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 = - Handle::weak_from_u128(0x7E896F48B707555DD11985F9C1594459); - pub const BINDINGS: Handle = Handle::weak_from_u128(0x140EFD89B5D4C8490AB895010DFC42FE); + weak_handle!("e2dccbb0-7322-444a-983b-e74d0a08bcda"); + pub const BINDINGS: Handle = weak_handle!("bcc55ce5-0fc4-451e-8393-1b9efd2612c4"); pub const TRANSMITTANCE_LUT: Handle = - Handle::weak_from_u128(0xEECBDEDFEED7F4EAFBD401BFAA5E0EFB); + weak_handle!("a4187282-8cb1-42d3-889c-cbbfb6044183"); pub const MULTISCATTERING_LUT: Handle = - Handle::weak_from_u128(0x65915B32C44B6287C0CCE1E70AF2936A); - pub const SKY_VIEW_LUT: Handle = - Handle::weak_from_u128(0x54136D7E6FFCD45BE38399A4E5ED7186); + 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 = - Handle::weak_from_u128(0x6FDEC284AD356B78C3A4D8ED4CBA0BC5); - pub const RENDER_SKY: Handle = - Handle::weak_from_u128(0x1951EB87C8A6129F0B541B1E4B3D4962); + weak_handle!("a3daf030-4b64-49ae-a6a7-354489597cbe"); + pub const RENDER_SKY: Handle = weak_handle!("09422f46-d0f7-41c1-be24-121c17d6e834"); } #[doc(hidden)] @@ -160,6 +159,15 @@ 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() diff --git a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl index 97a0c47b5154e..314037f887b3a 100644 --- a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl +++ b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl @@ -2,10 +2,10 @@ types::{Atmosphere, AtmosphereSettings}, bindings::{atmosphere, view, atmosphere_transforms}, functions::{ - sample_transmittance_lut, sample_sky_view_lut, - direction_world_to_atmosphere, uv_to_ray_direction, - uv_to_ndc, sample_aerial_view_lut, view_radius, - sample_sun_illuminance, + 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 }, }; #import bevy_render::view::View; @@ -18,22 +18,30 @@ @group(0) @binding(13) var depth_texture: texture_depth_2d; #endif +struct RenderSkyOutput { + @location(0) inscattering: vec4, + @location(0) @second_blend_source transmittance: vec4, +} + @fragment -fn main(in: FullscreenVertexOutput) -> @location(0) vec4 { +fn main(in: FullscreenVertexOutput) -> RenderSkyOutput { let depth = textureLoad(depth_texture, vec2(in.position.xy), 0); - if depth == 0.0 { - let ray_dir_ws = uv_to_ray_direction(in.uv); - let ray_dir_as = direction_world_to_atmosphere(ray_dir_ws.xyz); - let r = view_radius(); - let mu = ray_dir_ws.y; + let ray_dir_ws = uv_to_ray_direction(in.uv); + let r = view_radius(); + let mu = ray_dir_ws.y; - let transmittance = sample_transmittance_lut(r, mu); - let inscattering = sample_sky_view_lut(r, ray_dir_as); - - let sun_illuminance = sample_sun_illuminance(ray_dir_ws.xyz, transmittance); - return vec4(inscattering + sun_illuminance, (transmittance.r + transmittance.g + transmittance.b) / 3.0); + var transmittance: vec3; + var inscattering: vec3; + 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); } else { - return sample_aerial_view_lut(in.uv, depth); + 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); } + return RenderSkyOutput(vec4(inscattering, 0.0), vec4(transmittance, 1.0)); } diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index d37532e2250f5..71bf26da2d75a 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -367,7 +367,7 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts { blend: Some(BlendState { color: BlendComponent { src_factor: BlendFactor::One, - dst_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::Src1, operation: BlendOperation::Add, }, alpha: BlendComponent { diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index cc638151d0290..43edcd0bc34d5 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -17,7 +17,7 @@ use core::{num::NonZero, ops::Deref}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, AssetId, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::{require, Component}, @@ -54,7 +54,7 @@ use crate::{ /// The handle to the `clustered.wgsl` shader. pub(crate) const CLUSTERED_DECAL_SHADER_HANDLE: Handle = - Handle::weak_from_u128(2881025580737984685); + weak_handle!("87929002-3509-42f1-8279-2d2765dd145c"); /// The maximum number of decals that can be present in a view. /// diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index e580ae922a49b..7732f1d3a4ab3 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -3,7 +3,7 @@ use crate::{ MaterialPlugin, StandardMaterial, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Asset, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Asset, Assets, Handle}; use bevy_ecs::component::{require, Component}; use bevy_math::{prelude::Rectangle, Quat, Vec2, Vec3}; use bevy_reflect::{Reflect, TypePath}; @@ -14,10 +14,13 @@ use bevy_render::{ AsBindGroup, CompareFunction, RenderPipelineDescriptor, Shader, SpecializedMeshPipelineError, }, + RenderDebugFlags, }; -const FORWARD_DECAL_MESH_HANDLE: Handle = Handle::weak_from_u128(19376620402995522466); -const FORWARD_DECAL_SHADER_HANDLE: Handle = Handle::weak_from_u128(29376620402995522466); +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; @@ -46,6 +49,7 @@ impl Plugin for ForwardDecalPlugin { app.add_plugins(MaterialPlugin::> { prepass_enabled: false, shadows_enabled: false, + debug_flags: RenderDebugFlags::default(), ..Default::default() }); } diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 184249657aab2..c7d7e04636150 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, deferred::{ @@ -35,7 +35,7 @@ use bevy_render::{ pub struct DeferredPbrLightingPlugin; pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle = - Handle::weak_from_u128(2708011359337029741); + weak_handle!("f4295279-8890-4748-b654-ca4d2183df1c"); pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1; @@ -432,28 +432,26 @@ pub fn prepare_deferred_lighting_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, deferred_lighting_layout: Res, - views: Query< + views: Query<( + Entity, + &ExtractedView, + Option<&Tonemapping>, + Option<&DebandDither>, + Option<&ShadowFilteringMethod>, ( - Entity, - &ExtractedView, - Option<&Tonemapping>, - Option<&DebandDither>, - Option<&ShadowFilteringMethod>, - ( - Has, - Has, - Has, - ), - ( - Has, - Has, - Has, - ), - Has>, - Has>, + Has, + Has, + Has, + ), + ( + Has, + Has, + Has, + Has, ), - With, - >, + Has>, + Has>, + )>, ) { for ( entity, @@ -462,11 +460,19 @@ pub fn prepare_deferred_lighting_pipelines( dither, shadow_filter_method, (ssao, ssr, distance_fog), - (normal_prepass, depth_prepass, motion_vector_prepass), + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), has_environment_maps, has_irradiance_volumes, ) in &views { + // If there is no deferred prepass, remove the old pipeline if there was + // one. This handles the case in which a view using deferred stops using + // it. + if !deferred_prepass { + commands.entity(entity).remove::(); + continue; + } + let mut view_key = MeshPipelineKey::from_hdr(view.hdr); if normal_prepass { diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 17ea201561f60..68690439030fc 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -5,8 +5,9 @@ use bevy_render::{ alpha::AlphaMode, mesh::MeshVertexBufferLayoutRef, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader, - ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup, + AsBindGroup, AsBindGroupError, BindGroupLayout, BindlessDescriptor, + BindlessSlabResourceLimit, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipelineError, UnpreparedBindGroup, }, renderer::RenderDevice, }; @@ -153,13 +154,12 @@ impl AsBindGroup for ExtendedMaterial { type Data = (::Data, ::Data); type Param = (::Param, ::Param); - fn bindless_slot_count() -> Option { - match (B::bindless_slot_count(), E::bindless_slot_count()) { - (Some(base_bindless_slot_count), Some(extension_bindless_slot_count)) => { - Some(base_bindless_slot_count.min(extension_bindless_slot_count)) - } - _ => None, + 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") } + None } fn unprepared_bind_group( @@ -167,30 +167,15 @@ impl AsBindGroup for ExtendedMaterial { layout: &BindGroupLayout, render_device: &RenderDevice, (base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>, - mut force_no_bindless: bool, + _: bool, ) -> Result, AsBindGroupError> { - // Only allow bindless mode if both the base material and the extension - // support it. - force_no_bindless = force_no_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, - force_no_bindless, - )?; - let extended_bindgroup = E::unprepared_bind_group( - &self.extension, - layout, - render_device, - extended_param, - force_no_bindless, - )?; + } = 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)?; bindings.extend(extended_bindgroup.bindings.0); @@ -202,23 +187,24 @@ impl AsBindGroup for ExtendedMaterial { fn bind_group_layout_entries( render_device: &RenderDevice, - mut force_no_bindless: bool, + _: bool, ) -> Vec where Self: Sized, { - // Only allow bindless mode if both the base material and the extension - // support it. - force_no_bindless = force_no_bindless || Self::bindless_slot_count().is_none(); - // add together the bindings of the standard material and the user material - let mut entries = B::bind_group_layout_entries(render_device, force_no_bindless); - entries.extend(E::bind_group_layout_entries( - render_device, - force_no_bindless, - )); + let mut entries = B::bind_group_layout_entries(render_device, true); + entries.extend(E::bind_group_layout_entries(render_device, true)); entries } + + fn bindless_descriptor() -> Option { + if B::bindless_descriptor().is_some() && E::bindless_descriptor().is_some() { + panic!("Bindless extended materials are currently unsupported") + } + + None + } } impl Material for ExtendedMaterial { diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 089f38d47d30f..543a4665fc54d 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -45,8 +45,6 @@ mod ssao; mod ssr; mod volumetric_fog; -use crate::material_bind_groups::FallbackBindlessResources; - use bevy_color::{Color, LinearRgba}; pub use atmosphere::*; @@ -59,6 +57,7 @@ pub use light::*; pub use light_probe::*; pub use lightmap::*; pub use material::*; +pub use material_bind_groups::*; pub use mesh_material::*; pub use parallax::*; pub use pbr_material::*; @@ -88,29 +87,44 @@ pub mod prelude { pub mod graph { use bevy_render::render_graph::RenderLabel; + /// Render graph nodes specific to 3D PBR rendering. #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] pub enum NodePbr { - /// Label for the shadow pass node. - ShadowPass, + /// Label for the shadow pass node that draws meshes that were visible + /// from the light last frame. + EarlyShadowPass, + /// Label for the shadow pass node that draws meshes that became visible + /// from the light this frame. + LateShadowPass, /// Label for the screen space ambient occlusion render node. ScreenSpaceAmbientOcclusion, DeferredLightingPass, /// Label for the volumetric lighting pass. VolumetricFog, - /// Label for the compute shader instance data building pass. + /// Label for the shader that transforms and culls meshes that were + /// visible last frame. EarlyGpuPreprocess, + /// Label for the shader that transforms and culls meshes that became + /// visible this frame. LateGpuPreprocess, /// Label for the screen space reflections pass. ScreenSpaceReflections, + /// Label for the node that builds indirect draw parameters for meshes + /// that were visible last frame. EarlyPrepassBuildIndirectParameters, + /// Label for the node that builds indirect draw parameters for meshes + /// that became visible this frame. LatePrepassBuildIndirectParameters, + /// Label for the node that builds indirect draw parameters for the main + /// rendering pass, containing all meshes that are visible this frame. MainBuildIndirectParameters, + ClearIndirectParametersMetadata, } } use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr}; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, AssetApp, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetApp, Assets, Handle}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_ecs::prelude::*; use bevy_image::Image; @@ -125,35 +139,47 @@ use bevy_render::{ sync_component::SyncComponentPlugin, texture::GpuImage, view::VisibilitySystems, - ExtractSchedule, Render, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSet, }; use bevy_transform::TransformSystem; -pub const PBR_TYPES_SHADER_HANDLE: Handle = Handle::weak_from_u128(1708015359337029744); -pub const PBR_BINDINGS_SHADER_HANDLE: Handle = Handle::weak_from_u128(5635987986427308186); -pub const UTILS_HANDLE: Handle = Handle::weak_from_u128(1900548483293416725); -pub const CLUSTERED_FORWARD_HANDLE: Handle = Handle::weak_from_u128(166852093121196815); -pub const PBR_LIGHTING_HANDLE: Handle = Handle::weak_from_u128(14170772752254856967); -pub const PBR_TRANSMISSION_HANDLE: Handle = Handle::weak_from_u128(77319684653223658032); -pub const SHADOWS_HANDLE: Handle = Handle::weak_from_u128(11350275143789590502); -pub const SHADOW_SAMPLING_HANDLE: Handle = Handle::weak_from_u128(3145627513789590502); -pub const PBR_FRAGMENT_HANDLE: Handle = Handle::weak_from_u128(2295049283805286543); -pub const PBR_SHADER_HANDLE: Handle = Handle::weak_from_u128(4805239651767701046); -pub const PBR_PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(9407115064344201137); -pub const PBR_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(16550102964439850292); -pub const PBR_AMBIENT_HANDLE: Handle = Handle::weak_from_u128(2441520459096337034); +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 = - Handle::weak_from_u128(17035894873630133905); + weak_handle!("6cf57d9f-222a-429a-bba4-55ba9586e1d4"); pub const VIEW_TRANSFORMATIONS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(2098345702398750291); + weak_handle!("ec047703-cde3-4876-94df-fed121544abb"); pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(73204817249182637); -pub const PBR_DEFERRED_TYPES_HANDLE: Handle = Handle::weak_from_u128(3221241127431430599); -pub const PBR_DEFERRED_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(72019026415438599); -pub const RGB9E5_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(2659010996143919192); + 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 = - Handle::weak_from_u128(2325134235233421); + weak_handle!("69187376-3dea-4d0f-b3f5-185bde63d6a2"); pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 26; pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 27; @@ -170,6 +196,8 @@ pub struct PbrPlugin { /// This requires compute shader support and so will be forcibly disabled if /// the platform doesn't support those. pub use_gpu_instance_buffer_builder: bool, + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, } impl Default for PbrPlugin { @@ -178,6 +206,7 @@ impl Default for PbrPlugin { prepass_enabled: true, add_default_deferred_lighting_plugin: true, use_gpu_instance_buffer_builder: true, + debug_flags: RenderDebugFlags::default(), } } } @@ -321,9 +350,11 @@ impl Plugin for PbrPlugin { .add_plugins(( MeshRenderPlugin { use_gpu_instance_buffer_builder: self.use_gpu_instance_buffer_builder, + debug_flags: self.debug_flags, }, MaterialPlugin:: { prepass_enabled: self.prepass_enabled, + debug_flags: self.debug_flags, ..Default::default() }, ScreenSpaceAmbientOcclusionPlugin, @@ -411,7 +442,8 @@ impl Plugin for PbrPlugin { // NOTE: This MUST be scheduled AFTER the core renderer visibility check // because that resets entity `ViewVisibility` for the first view // which would override any results from this otherwise - .after(VisibilitySystems::CheckVisibility), + .after(VisibilitySystems::CheckVisibility) + .before(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible), ), ); @@ -455,11 +487,17 @@ impl Plugin for PbrPlugin { .add_observer(remove_light_view_entities); render_app.world_mut().add_observer(extracted_light_removed); - let shadow_pass_node = ShadowPassNode::new(render_app.world_mut()); + let early_shadow_pass_node = EarlyShadowPassNode::from_world(render_app.world_mut()); + let late_shadow_pass_node = LateShadowPassNode::from_world(render_app.world_mut()); let mut graph = render_app.world_mut().resource_mut::(); let draw_3d_graph = graph.get_sub_graph_mut(Core3d).unwrap(); - draw_3d_graph.add_node(NodePbr::ShadowPass, shadow_pass_node); - draw_3d_graph.add_node_edge(NodePbr::ShadowPass, Node3d::StartMainPass); + draw_3d_graph.add_node(NodePbr::EarlyShadowPass, early_shadow_pass_node); + draw_3d_graph.add_node(NodePbr::LateShadowPass, late_shadow_pass_node); + draw_3d_graph.add_node_edges(( + NodePbr::EarlyShadowPass, + NodePbr::LateShadowPass, + Node3d::StartMainPass, + )); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 713ac1a27d919..1c9dc61374a1e 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -13,8 +13,8 @@ use bevy_render::{ mesh::Mesh3d, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, view::{ - InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityClass, - VisibilityRange, VisibleEntityRanges, + InheritedVisibility, NoFrustumCulling, PreviousVisibleEntities, RenderLayers, + ViewVisibility, VisibilityClass, VisibilityRange, VisibleEntityRanges, }, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -814,15 +814,23 @@ pub fn check_dir_light_mesh_visibility( // TODO: use resource to avoid unnecessary memory alloc let mut defer_queue = core::mem::take(defer_visible_entities_queue.deref_mut()); commands.queue(move |world: &mut World| { - let mut query = world.query::<&mut ViewVisibility>(); - for entities in defer_queue.iter_mut() { - let mut iter = query.iter_many_mut(world, entities.iter()); - while let Some(mut view_visibility) = iter.fetch_next() { - if !**view_visibility { - view_visibility.set(); + world.resource_scope::( + |world, mut previous_visible_entities| { + let mut query = world.query::<(Entity, &mut ViewVisibility)>(); + for entities in defer_queue.iter_mut() { + let mut iter = query.iter_many_mut(world, entities.iter()); + while let Some((entity, mut view_visibility)) = iter.fetch_next() { + if !**view_visibility { + view_visibility.set(); + } + + // Remove any entities that were discovered to be + // visible from the `PreviousVisibleEntities` resource. + previous_visible_entities.remove(&entity); + } } - } - } + }, + ); }); } @@ -860,6 +868,7 @@ pub fn check_point_light_mesh_visibility( ), >, visible_entity_ranges: Option>, + mut previous_visible_entities: ResMut, mut cubemap_visible_entities_queue: Local; 6]>>, mut spot_visible_entities_queue: Local>>, mut checked_lights: Local, @@ -961,10 +970,17 @@ pub fn check_point_light_mesh_visibility( ); for entities in cubemap_visible_entities_queue.iter_mut() { - cubemap_visible_entities - .iter_mut() - .zip(entities.iter_mut()) - .for_each(|(dst, source)| dst.entities.append(source)); + for (dst, source) in + cubemap_visible_entities.iter_mut().zip(entities.iter_mut()) + { + // Remove any entities that were discovered to be + // visible from the `PreviousVisibleEntities` resource. + for entity in source.iter() { + previous_visible_entities.remove(entity); + } + + dst.entities.append(source); + } } for visible_entities in cubemap_visible_entities.iter_mut() { @@ -1047,6 +1063,12 @@ pub fn check_point_light_mesh_visibility( for entities in spot_visible_entities_queue.iter_mut() { visible_entities.append(entities); + + // Remove any entities that were discovered to be visible + // from the `PreviousVisibleEntities` resource. + for entity in entities { + previous_visible_entities.remove(entity); + } } shrink_entities(visible_entities.deref_mut()); diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 06985ef1b727e..8069f2acac752 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::{AssetId, Handle}; +use bevy_asset::{weak_handle, AssetId, Handle}; use bevy_ecs::{ component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read, }; @@ -74,7 +74,7 @@ use super::{LightProbeComponent, RenderViewLightProbes}; /// A handle to the environment map helper shader. pub const ENVIRONMENT_MAP_SHADER_HANDLE: Handle = - Handle::weak_from_u128(154476556247605696); + 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 b1e974711d882..799329e98284b 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -146,7 +146,7 @@ use bevy_render::{ use bevy_utils::default; use core::{num::NonZero, ops::Deref}; -use bevy_asset::{AssetId, Handle}; +use bevy_asset::{weak_handle, AssetId, Handle}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use crate::{ @@ -157,7 +157,7 @@ use crate::{ use super::LightProbeComponent; pub const IRRADIANCE_VOLUME_SHADER_HANDLE: Handle = - Handle::weak_from_u128(160299515939076705258408299184317675488); + 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 diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 5cf96965ad989..3ef3e78db85f4 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, AssetId, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; use bevy_core_pipeline::core_3d::Camera3d; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -43,7 +43,8 @@ use crate::{ use self::irradiance_volume::IrradianceVolume; -pub const LIGHT_PROBE_SHADER_HANDLE: Handle = Handle::weak_from_u128(8954249792581071582); +pub const LIGHT_PROBE_SHADER_HANDLE: Handle = + weak_handle!("e80a2ae6-1c5a-4d9a-a852-d66ff0e6bf7f"); pub mod environment_map; pub mod irradiance_volume; diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index 3809ea711bc36..afaa71d7926bd 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, AssetId, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -68,7 +68,7 @@ use crate::{binding_arrays_are_usable, ExtractMeshesSet}; /// The ID of the lightmap shader. pub const LIGHTMAP_SHADER_HANDLE: Handle = - Handle::weak_from_u128(285484768317531991932943596447919767152); + weak_handle!("fc28203f-f258-47f3-973c-ce7d1dd70e59"); /// The number of lightmaps that we store in a single slab, if bindless textures /// are in use. diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 96ccdf781e840..b32d574400825 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,25 +1,27 @@ -use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; -use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId}; +use crate::material_bind_groups::{ + FallbackBindlessResources, MaterialBindGroupAllocator, MaterialBindingId, +}; #[cfg(feature = "meshlet")] use crate::meshlet::{ prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes, InstanceManager, }; use crate::*; -use bevy_asset::{Asset, AssetId, AssetServer, UntypedAssetId}; +use bevy_asset::prelude::AssetChanged; +use bevy_asset::{Asset, AssetEvents, AssetId, AssetServer, UntypedAssetId}; +use bevy_core_pipeline::deferred::{AlphaMask3dDeferred, Opaque3dDeferred}; +use bevy_core_pipeline::prepass::{AlphaMask3dPrepass, Opaque3dPrepass}; use bevy_core_pipeline::{ core_3d::{ - AlphaMask3d, Camera3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, - ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, - }, - oit::OrderIndependentTransparencySettings, - prepass::{ - DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, - OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, + AlphaMask3d, Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality, + Transmissive3d, Transparent3d, }, - tonemapping::{DebandDither, Tonemapping}, + prepass::{OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey}, + tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, system::{ @@ -27,20 +29,23 @@ use bevy_ecs::{ SystemParamItem, }, }; -use bevy_platform_support::collections::HashMap; +use bevy_platform_support::collections::hash_map::Entry; +use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_platform_support::hash::FixedHasher; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::mesh::mark_3d_meshes_as_changed_if_their_assets_changed; +use bevy_render::renderer::RenderQueue; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, - camera::TemporalJitter, extract_resource::ExtractResource, - mesh::{self, Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, + mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::*, render_resource::*, renderer::RenderDevice, sync_world::MainEntity, - view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility}, + view::{ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity, ViewVisibility}, Extract, }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; @@ -251,6 +256,8 @@ pub struct MaterialPlugin { pub prepass_enabled: bool, /// Controls if shadows are enabled for the Material. pub shadows_enabled: bool, + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, pub _marker: PhantomData, } @@ -259,6 +266,7 @@ impl Default for MaterialPlugin { Self { prepass_enabled: true, shadows_enabled: true, + debug_flags: RenderDebugFlags::default(), _marker: Default::default(), } } @@ -271,16 +279,29 @@ where fn build(&self, app: &mut App) { app.init_asset::() .register_type::>() - .add_plugins(RenderAssetPlugin::>::default()) + .init_resource::>() + .add_plugins((RenderAssetPlugin::>::default(),)) .add_systems( PostUpdate, - mark_meshes_as_changed_if_their_materials_changed:: - .ambiguous_with_all() - .after(mesh::mark_3d_meshes_as_changed_if_their_assets_changed), + ( + mark_meshes_as_changed_if_their_materials_changed::.ambiguous_with_all(), + check_entities_needing_specialization::.after(AssetEvents), + ) + .after(mark_3d_meshes_as_changed_if_their_assets_changed), + ); + + if self.shadows_enabled { + app.add_systems( + PostUpdate, + check_light_entities_needing_specialization:: + .after(check_entities_needing_specialization::), ); + } if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() .init_resource::>() .init_resource::>() .add_render_command::>() @@ -291,28 +312,52 @@ where .init_resource::>>() .add_systems( ExtractSchedule, - extract_mesh_materials::.before(ExtractMeshesSet), + ( + extract_mesh_materials::.before(ExtractMeshesSet), + extract_entities_needs_specialization::, + ), ) .add_systems( Render, - queue_material_meshes:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), + ( + specialize_material_meshes:: + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::>) + .after(prepare_assets::) + .after(collect_meshes_for_gpu_building), + queue_material_meshes:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), ) .add_systems( Render, - prepare_material_bind_groups:: + ( + prepare_material_bind_groups::, + write_material_bind_group_buffers::, + ) + .chain() .in_set(RenderSet::PrepareBindGroups) .after(prepare_assets::>), ); if self.shadows_enabled { - render_app.add_systems( - Render, - queue_shadows:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), - ); + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .add_systems( + Render, + ( + check_views_lights_need_specialization.in_set(RenderSet::PrepareAssets), + specialize_shadows:: + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::>), + queue_shadows:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), + ); } #[cfg(feature = "meshlet")] @@ -340,7 +385,7 @@ where } if self.prepass_enabled { - app.add_plugins(PrepassPlugin::::default()); + app.add_plugins(PrepassPlugin::::new(self.debug_flags)); } } @@ -472,7 +517,7 @@ impl FromWorld for MaterialPipeline { ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, - bindless: material_bind_groups::material_uses_bindless_resources::(render_device), + bindless: material_uses_bindless_resources::(render_device), marker: PhantomData, } } @@ -523,7 +568,7 @@ impl RenderCommand

for SetMaterial else { return RenderCommandResult::Skip; }; - let Some(bind_group) = material_bind_group.get_bind_group() else { + let Some(bind_group) = material_bind_group.bind_group() else { return RenderCommandResult::Skip; }; pass.set_bind_group(I, bind_group, &[]); @@ -653,180 +698,178 @@ fn extract_mesh_materials( } } -/// For each view, iterates over all the meshes visible from that view and adds -/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. -pub fn queue_material_meshes( - ( - opaque_draw_functions, - alpha_mask_draw_functions, - transmissive_draw_functions, - transparent_draw_functions, - ): ( - Res>, - Res>, - Res>, - Res>, - ), - material_pipeline: Res>, - mut pipelines: ResMut>>, - pipeline_cache: Res, - render_meshes: Res>, - render_materials: Res>>, - render_mesh_instances: Res, - render_material_instances: Res>, - render_lightmaps: Res, - render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator, gpu_preprocessing_support): ( - Res, - Res>, - Res, - ), - mut opaque_render_phases: ResMut>, - mut alpha_mask_render_phases: ResMut>, - mut transmissive_render_phases: ResMut>, - mut transparent_render_phases: ResMut>, - views: Query<( - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - Option<&ShadowFilteringMethod>, - Has, - ( - Has, - Has, - Has, - Has, - ), - Option<&Camera3d>, - Has, - Option<&Projection>, - Has, - ( - Has>, - Has>, - ), - Has, - )>, +pub fn extract_entities_needs_specialization( + entities_needing_specialization: Extract>>, + mut entity_specialization_ticks: ResMut>, + ticks: SystemChangeTick, ) where - M::Data: PartialEq + Eq + Hash + Clone, + M: Material, { - for ( - view, - visible_entities, - msaa, - tonemapping, - dither, - shadow_filter_method, - ssao, - (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), - camera_3d, - temporal_jitter, - projection, - distance_fog, - (has_environment_maps, has_irradiance_volumes), - has_oit, - ) in &views - { - let ( - Some(opaque_phase), - Some(alpha_mask_phase), - Some(transmissive_phase), - Some(transparent_phase), - ) = ( - opaque_render_phases.get_mut(&view.retained_view_entity), - alpha_mask_render_phases.get_mut(&view.retained_view_entity), - transmissive_render_phases.get_mut(&view.retained_view_entity), - transparent_render_phases.get_mut(&view.retained_view_entity), - ) - else { - continue; - }; - - let draw_opaque_pbr = opaque_draw_functions.read().id::>(); - let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); - let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); - let draw_transparent_pbr = transparent_draw_functions.read().id::>(); + 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()); + } +} - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); +#[derive(Resource, Deref, DerefMut, Clone, Debug)] +pub struct EntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, + _marker: PhantomData, +} - if normal_prepass { - view_key |= MeshPipelineKey::NORMAL_PREPASS; +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), } + } +} - if depth_prepass { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } +#[derive(Resource, Deref, DerefMut, Clone, Debug)] +pub struct EntitySpecializationTicks { + #[deref] + pub entities: MainEntityHashMap, + _marker: PhantomData, +} - if motion_vector_prepass { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), } + } +} - if deferred_prepass { - view_key |= MeshPipelineKey::DEFERRED_PREPASS; - } +/// Stores the [`SpecializedMaterialViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedMaterialPipelineCache { + // view entity -> view pipeline cache + #[deref] + map: HashMap>, + marker: PhantomData, +} - if temporal_jitter { - view_key |= MeshPipelineKey::TEMPORAL_JITTER; - } +/// 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)] +pub struct SpecializedMaterialViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, + marker: PhantomData, +} - if has_environment_maps { - view_key |= MeshPipelineKey::ENVIRONMENT_MAP; +impl Default for SpecializedMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, } + } +} - if has_irradiance_volumes { - view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; +impl Default for SpecializedMaterialViewPipelineCache { + fn default() -> Self { + Self { + map: MainEntityHashMap::default(), + marker: PhantomData, } + } +} - if has_oit { - view_key |= MeshPipelineKey::OIT_ENABLED; - } +pub fn check_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + >, + mut entities_needing_specialization: ResMut>, +) where + M: Material, +{ + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } +} - if let Some(projection) = projection { - view_key |= match projection { - Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, - Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, - Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, - }; +pub fn specialize_material_meshes( + render_meshes: Res>, + render_materials: Res>>, + render_mesh_instances: Res, + render_material_instances: Res>, + render_lightmaps: Res, + render_visibility_ranges: Res, + ( + material_bind_group_allocator, + opaque_render_phases, + alpha_mask_render_phases, + transmissive_render_phases, + transparent_render_phases, + ): ( + Res>, + Res>, + Res>, + Res>, + 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, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + // Record the retained IDs of all shadow 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 !transparent_render_phases.contains_key(&view.retained_view_entity) + && !opaque_render_phases.contains_key(&view.retained_view_entity) + && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) + && !transmissive_render_phases.contains_key(&view.retained_view_entity) + { + continue; } - match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { - ShadowFilteringMethod::Hardware2x2 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; - } - ShadowFilteringMethod::Gaussian => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; - } - ShadowFilteringMethod::Temporal => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; - } - } + let Some(view_key) = view_key_cache.get(&view.retained_view_entity) else { + continue; + }; - if !view.hdr { - if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; + 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::() { + 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; } - } - if ssao { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; - } - if distance_fog { - view_key |= MeshPipelineKey::DISTANCE_FOG; - } - if let Some(camera_3d) = camera_3d { - view_key |= screen_space_specular_transmission_pipeline_key( - camera_3d.screen_space_specular_transmission_quality, - ); - } - - let rangefinder = view.rangefinder3d(); - for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -849,15 +892,13 @@ pub fn queue_material_meshes( let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits; mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( material.properties.alpha_mode, - msaa, + &Msaa::from_samples(view_key.msaa_samples()), )); - let mut mesh_key = view_key + let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) | mesh_pipeline_key_bits; - let mut lightmap_slab = None; if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) { - lightmap_slab = Some(*lightmap.slab_index); mesh_key |= MeshPipelineKey::LIGHTMAPPED; if lightmap.bicubic_sampling { @@ -869,7 +910,7 @@ pub fn queue_material_meshes( mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } - if motion_vector_prepass { + if view_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { // If the previous frame have skins or morph targets, note that. if mesh_instance .flags @@ -885,17 +926,13 @@ pub fn queue_material_meshes( } } - let pipeline_id = pipelines.specialize( - &pipeline_cache, - &material_pipeline, - MaterialPipelineKey { - mesh_key, - bind_group_data: material_bind_group - .get_extra_data(material.binding.slot) - .clone(), - }, - &mesh.layout, - ); + let key = MaterialPipelineKey { + mesh_key, + bind_group_data: material_bind_group + .get_extra_data(material.binding.slot) + .clone(), + }; + let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, key, &mesh.layout); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { @@ -904,90 +941,156 @@ pub fn queue_material_meshes( } }; + 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)); +} + +/// For each view, iterates over all the meshes visible from that view and adds +/// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. +pub fn queue_material_meshes( + render_materials: Res>>, + render_mesh_instances: Res, + render_material_instances: Res>, + mesh_allocator: Res, + gpu_preprocessing_support: Res, + mut opaque_render_phases: ResMut>, + mut alpha_mask_render_phases: ResMut>, + mut transmissive_render_phases: ResMut>, + mut transparent_render_phases: ResMut>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + specialized_material_pipeline_cache: ResMut>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + for (view, visible_entities) in &views { + let ( + Some(opaque_phase), + Some(alpha_mask_phase), + Some(transmissive_phase), + Some(transparent_phase), + ) = ( + opaque_render_phases.get_mut(&view.retained_view_entity), + alpha_mask_render_phases.get_mut(&view.retained_view_entity), + transmissive_render_phases.get_mut(&view.retained_view_entity), + transparent_render_phases.get_mut(&view.retained_view_entity), + ) + else { + continue; + }; + + let Some(view_specialized_material_pipeline_cache) = + specialized_material_pipeline_cache.get(&view.retained_view_entity) + else { + continue; + }; + + let rangefinder = view.rangefinder3d(); + for (render_entity, visible_entity) in visible_entities.iter::() { + 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 opaque_phase.validate_cached_entity(*visible_entity, current_change_tick) + || alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick) + { + 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 { + continue; + }; + // Fetch the slabs that this mesh resides in. let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - match mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD) - { - MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => { - if material.properties.reads_view_transmission_texture { - let distance = rangefinder.distance_translation(&mesh_instance.translation) - + material.properties.depth_bias; - transmissive_phase.add(Transmissive3d { - entity: (*render_entity, *visible_entity), - draw_function: draw_transmissive_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } else if material.properties.render_method == OpaqueRendererMethod::Forward { - let batch_set_key = Opaque3dBatchSetKey { - pipeline: pipeline_id, - draw_function: draw_opaque_pbr, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - lightmap_slab, - }; - let bin_key = Opaque3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }; - opaque_phase.add( - batch_set_key, - bin_key, - (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - ); + match material.properties.render_phase_type { + RenderPhaseType::Transmissive => { + let distance = rangefinder.distance_translation(&mesh_instance.translation) + + material.properties.depth_bias; + transmissive_phase.add(Transmissive3d { + entity: (*render_entity, *visible_entity), + draw_function: material.properties.draw_function_id, + pipeline: pipeline_id, + distance, + batch_range: 0..1, + extra_index: PhaseItemExtraIndex::None, + indexed: index_slab.is_some(), + }); + } + RenderPhaseType::Opaque => { + if material.properties.render_method == OpaqueRendererMethod::Deferred { + continue; } + let batch_set_key = Opaque3dBatchSetKey { + pipeline: pipeline_id, + draw_function: material.properties.draw_function_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + lightmap_slab: mesh_instance.shared.lightmap_slab_index.map(|index| *index), + }; + let bin_key = Opaque3dBinKey { + asset_id: mesh_instance.mesh_asset_id.into(), + }; + opaque_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, + ); } // Alpha mask - MeshPipelineKey::MAY_DISCARD => { - if material.properties.reads_view_transmission_texture { - let distance = rangefinder.distance_translation(&mesh_instance.translation) - + material.properties.depth_bias; - transmissive_phase.add(Transmissive3d { - entity: (*render_entity, *visible_entity), - draw_function: draw_transmissive_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - extra_index: PhaseItemExtraIndex::None, - indexed: index_slab.is_some(), - }); - } else if material.properties.render_method == OpaqueRendererMethod::Forward { - let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - material_bind_group_index: Some(material.binding.group.0), - vertex_slab: vertex_slab.unwrap_or_default(), - index_slab, - }; - let bin_key = OpaqueNoLightmap3dBinKey { - asset_id: mesh_instance.mesh_asset_id.into(), - }; - alpha_mask_phase.add( - batch_set_key, - bin_key, - (*render_entity, *visible_entity), - BinnedRenderPhaseType::mesh( - mesh_instance.should_batch(), - &gpu_preprocessing_support, - ), - ); - } + RenderPhaseType::AlphaMask => { + let batch_set_key = OpaqueNoLightmap3dBatchSetKey { + draw_function: material.properties.draw_function_id, + pipeline: pipeline_id, + material_bind_group_index: Some(material.binding.group.0), + vertex_slab: vertex_slab.unwrap_or_default(), + index_slab, + }; + let bin_key = OpaqueNoLightmap3dBinKey { + asset_id: mesh_instance.mesh_asset_id.into(), + }; + alpha_mask_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, + ); } - _ => { + RenderPhaseType::Transparent => { let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transparent_phase.add(Transparent3d { entity: (*render_entity, *visible_entity), - draw_function: draw_transparent_pbr, + draw_function: material.properties.draw_function_id, pipeline: pipeline_id, distance, batch_range: 0..1, @@ -1070,6 +1173,18 @@ pub struct MaterialProperties { /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires /// rendering to take place in a separate [`Transmissive3d`] pass. pub reads_view_transmission_texture: bool, + pub render_phase_type: RenderPhaseType, + pub draw_function_id: DrawFunctionId, + pub prepass_draw_function_id: Option, + pub deferred_draw_function_id: Option, +} + +#[derive(Clone, Copy)] +pub enum RenderPhaseType { + Opaque, + AlphaMask, + Transmissive, + Transparent, } /// A resource that maps each untyped material ID to its binding. @@ -1096,6 +1211,14 @@ impl RenderAsset for PreparedMaterial { SRes, SResMut>, SResMut, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, + SRes>, M::Param, ); @@ -1108,25 +1231,75 @@ impl RenderAsset for PreparedMaterial { default_opaque_render_method, ref mut bind_group_allocator, ref mut render_material_bindings, + opaque_draw_functions, + alpha_mask_draw_functions, + transmissive_draw_functions, + transparent_draw_functions, + opaque_prepass_draw_functions, + alpha_mask_prepass_draw_functions, + opaque_deferred_draw_functions, + alpha_mask_deferred_draw_functions, ref mut material_param, ): &mut SystemParamItem, ) -> Result> { - // Allocate a material binding ID if needed. - let material_binding_id = *render_material_bindings - .entry(material_id.into()) - .or_insert_with(|| bind_group_allocator.allocate()); - - let method = match material.opaque_render_method() { + let draw_opaque_pbr = opaque_draw_functions.read().id::>(); + let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); + let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); + let draw_transparent_pbr = transparent_draw_functions.read().id::>(); + let draw_opaque_prepass = opaque_prepass_draw_functions + .read() + .get_id::>(); + let draw_alpha_mask_prepass = alpha_mask_prepass_draw_functions + .read() + .get_id::>(); + let draw_opaque_deferred = opaque_deferred_draw_functions + .read() + .get_id::>(); + let draw_alpha_mask_deferred = alpha_mask_deferred_draw_functions + .read() + .get_id::>(); + + let render_method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, OpaqueRendererMethod::Auto => default_opaque_render_method.0, }; + let mut mesh_pipeline_key_bits = MeshPipelineKey::empty(); mesh_pipeline_key_bits.set( MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE, material.reads_view_transmission_texture(), ); + let reads_view_transmission_texture = + mesh_pipeline_key_bits.contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE); + + let render_phase_type = match material.alpha_mode() { + AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => { + RenderPhaseType::Transparent + } + _ if reads_view_transmission_texture => RenderPhaseType::Transmissive, + AlphaMode::Opaque | AlphaMode::AlphaToCoverage => RenderPhaseType::Opaque, + AlphaMode::Mask(_) => RenderPhaseType::AlphaMask, + }; + + let draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => draw_opaque_pbr, + RenderPhaseType::AlphaMask => draw_alpha_mask_pbr, + RenderPhaseType::Transmissive => draw_transmissive_pbr, + RenderPhaseType::Transparent => draw_transparent_pbr, + }; + let prepass_draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => draw_opaque_prepass, + RenderPhaseType::AlphaMask => draw_alpha_mask_prepass, + _ => None, + }; + let deferred_draw_function_id = match render_phase_type { + RenderPhaseType::Opaque => draw_opaque_deferred, + RenderPhaseType::AlphaMask => draw_alpha_mask_deferred, + _ => None, + }; + match material.unprepared_bind_group( &pipeline.material_layout, render_device, @@ -1134,17 +1307,37 @@ impl RenderAsset for PreparedMaterial { false, ) { Ok(unprepared) => { - bind_group_allocator.init(render_device, material_binding_id, unprepared); + // Allocate or update the material. + let binding = match render_material_bindings.entry(material_id.into()) { + Entry::Occupied(mut occupied_entry) => { + // TODO: Have a fast path that doesn't require + // recreating the bind group if only buffer contents + // change. For now, we just delete and recreate the bind + // group. + bind_group_allocator.free(*occupied_entry.get()); + let new_binding = bind_group_allocator + .allocate_unprepared(unprepared, &pipeline.material_layout); + *occupied_entry.get_mut() = new_binding; + new_binding + } + Entry::Vacant(vacant_entry) => *vacant_entry.insert( + bind_group_allocator + .allocate_unprepared(unprepared, &pipeline.material_layout), + ), + }; Ok(PreparedMaterial { - binding: material_binding_id, + binding, properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), - reads_view_transmission_texture: mesh_pipeline_key_bits - .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), - render_method: method, + reads_view_transmission_texture, + render_phase_type, + draw_function_id, + prepass_draw_function_id, + render_method, mesh_pipeline_key_bits, + deferred_draw_function_id, }, phantom: PhantomData, }) @@ -1166,21 +1359,22 @@ impl RenderAsset for PreparedMaterial { ) { Ok(prepared_bind_group) => { // Store the resulting bind group directly in the slot. - bind_group_allocator.init_custom( - material_binding_id, - prepared_bind_group.bind_group, - prepared_bind_group.data, - ); + let material_binding_id = + bind_group_allocator.allocate_prepared(prepared_bind_group); + render_material_bindings.insert(material_id.into(), material_binding_id); Ok(PreparedMaterial { binding: material_binding_id, properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), - reads_view_transmission_texture: mesh_pipeline_key_bits - .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), - render_method: method, + reads_view_transmission_texture, + render_phase_type, + draw_function_id, + prepass_draw_function_id, + render_method, mesh_pipeline_key_bits, + deferred_draw_function_id, }, phantom: PhantomData, }) @@ -1206,7 +1400,7 @@ impl RenderAsset for PreparedMaterial { _, ref mut bind_group_allocator, ref mut render_material_bindings, - _, + .., ): &mut SystemParamItem, ) { let Some(material_binding_id) = render_material_bindings.remove(&source_asset.untyped()) @@ -1232,8 +1426,8 @@ impl From for MaterialBindGroupId { } } -/// A system that creates and/or recreates any bind groups that contain -/// materials that were modified this frame. +/// Creates and/or recreates any bind groups that contain materials that were +/// modified this frame. pub fn prepare_material_bind_groups( mut allocator: ResMut>, render_device: Res, @@ -1242,5 +1436,20 @@ pub fn prepare_material_bind_groups( ) where M: Material, { - allocator.prepare_bind_groups(&render_device, &fallback_image, &fallback_resources); + allocator.prepare_bind_groups(&render_device, &fallback_resources, &fallback_image); +} + +/// Uploads the contents of all buffers that the [`MaterialBindGroupAllocator`] +/// manages to the GPU. +/// +/// Non-bindless allocators don't currently manage any buffers, so this method +/// only has an effect for bindless allocators. +pub fn write_material_bind_group_buffers( + mut allocator: ResMut>, + render_device: Res, + render_queue: Res, +) where + M: Material, +{ + allocator.write_buffers(&render_device, &render_queue); } diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index 9066537a72ad8..d85a490a3f6b9 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -1,137 +1,258 @@ //! Material bind group management for bindless resources. //! -//! In bindless mode, Bevy's renderer groups materials into small bind groups. -//! This allocator manages each bind group, assigning slots to materials as +//! In bindless mode, Bevy's renderer groups materials into bind groups. This +//! allocator manages each bind group, assigning slots to materials as //! appropriate. -use crate::Material; +use core::{marker::PhantomData, mem}; + use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ resource::Resource, world::{FromWorld, World}, }; -use bevy_platform_support::collections::HashMap; -use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_platform_support::collections::{HashMap, HashSet}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ render_resource::{ - BindGroup, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, BindingResource, - BindingType, Buffer, BufferBinding, BufferInitDescriptor, BufferUsages, - OwnedBindingResource, Sampler, SamplerDescriptor, TextureViewDimension, - UnpreparedBindGroup, WgpuSampler, WgpuTextureView, + BindGroup, BindGroupEntry, BindGroupLayout, BindingNumber, BindingResource, + BindingResources, BindlessDescriptor, BindlessIndex, BindlessResourceType, Buffer, + BufferBinding, BufferDescriptor, BufferId, BufferUsages, CompareFunction, FilterMode, + OwnedBindingResource, PreparedBindGroup, RawBufferVec, Sampler, SamplerDescriptor, + SamplerId, TextureView, TextureViewDimension, TextureViewId, UnpreparedBindGroup, + WgpuSampler, WgpuTextureView, }, - renderer::RenderDevice, + renderer::{RenderDevice, RenderQueue}, texture::FallbackImage, }; use bevy_utils::default; -use core::{any, iter, marker::PhantomData, num::NonZero}; -use tracing::error; +use tracing::{error, trace}; + +use crate::Material; -/// An object that creates and stores bind groups for a single material type. +/// A resource that places materials into bind groups and tracks their +/// resources. /// -/// This object collects bindless materials into groups as appropriate and -/// assigns slots as materials are created. +/// Internally, Bevy has separate allocators for bindless and non-bindless +/// materials. This resource provides a common interface to the specific +/// allocator in use. #[derive(Resource)] -pub struct MaterialBindGroupAllocator +pub enum MaterialBindGroupAllocator where M: Material, { - /// The data that the allocator keeps about each bind group. - bind_groups: Vec>, - - /// Stores IDs of material bind groups that have at least one slot - /// available. - free_bind_groups: Vec, + /// The allocator used when the material is bindless. + Bindless(Box>), + /// The allocator used when the material is non-bindless. + NonBindless(Box>), +} - /// The layout for this bind group. +/// The allocator that places bindless materials into bind groups and tracks +/// their resources. +pub struct MaterialBindGroupBindlessAllocator +where + M: Material, +{ + /// The slabs, each of which contains a bind group. + slabs: Vec>, + /// The layout of the bind groups that we produce. bind_group_layout: BindGroupLayout, + /// Information about the bindless resources in the material. + /// + /// We use this information to create and maintain bind groups. + bindless_descriptor: BindlessDescriptor, - /// Dummy buffers that are assigned to unused slots. - fallback_buffers: MaterialFallbackBuffers, - - /// Whether this material is actually using bindless resources. + /// Dummy buffers that we use to fill empty slots in buffer binding arrays. /// - /// This takes the availability of bindless resources on this platform into - /// account. - bindless_enabled: bool, + /// There's one fallback buffer for each buffer in the bind group, each + /// appropriately sized. Each buffer contains one uninitialized element of + /// the applicable type. + fallback_buffers: HashMap, - phantom: PhantomData, + /// The maximum number of resources that can be stored in a slab. + /// + /// This corresponds to `SLAB_CAPACITY` in the `#[bindless(SLAB_CAPACITY)]` + /// attribute, when deriving `AsBindGroup`. + slab_capacity: u32, } -/// Information that the allocator keeps about each bind group. -pub enum MaterialBindGroup +/// A single bind group and the bookkeeping necessary to allocate into it. +pub struct MaterialBindlessSlab where M: Material, { - /// Information that the allocator keeps about each bind group with bindless - /// textures in use. - Bindless(MaterialBindlessBindGroup), + /// The current bind group, if it's up to date. + /// + /// If this is `None`, then the bind group is dirty and needs to be + /// regenerated. + bind_group: Option, - /// Information that the allocator keeps about each bind group for which - /// bindless textures are not in use. - NonBindless(MaterialNonBindlessBindGroup), + /// A GPU-accessible buffer that holds the mapping from binding index to + /// bindless slot. + /// + /// This is conventionally assigned to bind group binding 0. + bindless_index_table: MaterialBindlessIndexTable, + + /// The binding arrays containing samplers. + samplers: HashMap>, + /// The binding arrays containing textures. + textures: HashMap>, + /// The binding arrays containing data buffers. + buffers: HashMap>, + + /// Holds extra CPU-accessible data that the material provides. + /// + /// Typically, this data is used for constructing the material key, for + /// pipeline specialization purposes. + extra_data: Vec>, + + /// A list of free slot IDs. + free_slots: Vec, + /// The total number of materials currently allocated in this slab. + live_allocation_count: u32, + /// The total number of resources currently allocated in the binding arrays. + allocated_resource_count: u32, } -/// Information that the allocator keeps about each bind group with bindless -/// textures in use. -pub struct MaterialBindlessBindGroup +/// A GPU-accessible buffer that holds the mapping from binding index to +/// bindless slot. +/// +/// This is conventionally assigned to bind group binding 0. +struct MaterialBindlessIndexTable where M: Material, { - /// The actual bind group. - pub bind_group: Option, - - /// The bind group data for each slot. - /// - /// This is `None` if the slot is unallocated and `Some` if the slot is - /// full. - unprepared_bind_groups: Vec>>, + /// The contents of the buffer. + buffer: RawBufferVec, + /// Whether the contents of the buffer have been uploaded to the GPU. + buffer_dirty: BufferDirtyState, + phantom: PhantomData, +} - /// A bitfield that contains a 0 if the slot is free or a 1 if the slot is - /// full. +/// A single binding array for storing bindless resources and the bookkeeping +/// necessary to allocate into it. +struct MaterialBindlessBindingArray +where + R: GetBindingResourceId, +{ + /// The number of the binding that we attach this binding array to. + binding_number: BindingNumber, + /// A mapping from bindless slot index to the resource stored in that slot, + /// if any. + bindings: Vec>>, + /// The type of resource stored in this binding array. + resource_type: BindlessResourceType, + /// Maps a resource ID to the slot in which it's stored. /// - /// We keep this value so that we can quickly find the next free slot when - /// we go to allocate. - used_slot_bitmap: u32, + /// This is essentially the inverse mapping of [`Self::bindings`]. + resource_to_slot: HashMap, + /// A list of free slots in [`Self::bindings`] that contain no binding. + free_slots: Vec, + /// The number of allocated objects in this binding array. + len: u32, } -/// Information that the allocator keeps about each bind group for which -/// bindless textures are not in use. +/// A single resource (sampler, texture, or buffer) in a binding array. /// -/// When a bindless texture isn't in use, bind groups and material instances are -/// in 1:1 correspondence, and therefore there's only a single slot for extra -/// material data here. -pub struct MaterialNonBindlessBindGroup +/// Resources hold a reference count, which specifies the number of materials +/// currently allocated within the slab that refer to this resource. When the +/// reference count drops to zero, the resource is freed. +struct MaterialBindlessBinding +where + R: GetBindingResourceId, +{ + /// The sampler, texture, or buffer. + resource: R, + /// The number of materials currently allocated within the containing slab + /// that use this resource. + ref_count: u32, +} + +/// The allocator that stores bind groups for non-bindless materials. +pub struct MaterialBindGroupNonBindlessAllocator where M: Material, { - /// The single allocation in a non-bindless bind group. - allocation: MaterialNonBindlessBindGroupAllocation, + /// A mapping from [`MaterialBindGroupIndex`] to the bind group allocated in + /// each slot. + bind_groups: Vec>>, + /// The bind groups that are dirty and need to be prepared. + /// + /// To prepare the bind groups, call + /// [`MaterialBindGroupAllocator::prepare_bind_groups`]. + to_prepare: HashSet, + /// A list of free bind group indices. + free_indices: Vec, + phantom: PhantomData, } -/// The single allocation in a non-bindless bind group. -enum MaterialNonBindlessBindGroupAllocation +/// A single bind group that a [`MaterialBindGroupNonBindlessAllocator`] is +/// currently managing. +enum MaterialNonBindlessAllocatedBindGroup where M: Material, { - /// The allocation is free. - Unallocated, - /// The allocation has been allocated, but not yet initialized. - Allocated, - /// The allocation is full and contains both a bind group and extra data. - Initialized(BindGroup, M::Data), + /// An unprepared bind group. + /// + /// The allocator prepares all outstanding unprepared bind groups when + /// [`MaterialBindGroupNonBindlessAllocator::prepare_bind_groups`] is + /// called. + Unprepared { + /// The unprepared bind group, including extra data. + bind_group: UnpreparedBindGroup, + /// The layout of that bind group. + layout: BindGroupLayout, + }, + /// A bind group that's already been prepared. + Prepared(PreparedBindGroup), +} + +/// Dummy instances of various resources that we fill unused slots in binding +/// arrays with. +#[derive(Resource)] +pub struct FallbackBindlessResources { + /// A dummy filtering sampler. + filtering_sampler: Sampler, + /// A dummy non-filtering sampler. + non_filtering_sampler: Sampler, + /// A dummy comparison sampler. + comparison_sampler: Sampler, } -/// Where the GPU data for a material is located. +/// The `wgpu` ID of a single bindless or non-bindless resource. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +enum BindingResourceId { + /// A buffer. + Buffer(BufferId), + /// A texture view, with the given dimension. + TextureView(TextureViewDimension, TextureViewId), + /// A sampler. + Sampler(SamplerId), +} + +/// A temporary list of references to `wgpu` bindless resources. /// -/// In bindless mode, materials are gathered into bind groups, and the slot is -/// necessary to locate the material data within that group. If not in bindless -/// mode, bind groups and materials are in 1:1 correspondence, and the slot -/// index is always 0. +/// We need this because the `wgpu` bindless API takes a slice of references. +/// Thus we need to create intermediate vectors of bindless resources in order +/// to satisfy `wgpu`'s lifetime requirements. +enum BindingResourceArray<'a> { + /// A list of bindings. + Buffers(Vec>), + /// A list of texture views. + TextureViews(Vec<&'a WgpuTextureView>), + /// A list of samplers. + Samplers(Vec<&'a WgpuSampler>), +} + +/// The location of a material (either bindless or non-bindless) within the +/// slabs. #[derive(Clone, Copy, Debug, Default, Reflect)] pub struct MaterialBindingId { /// The index of the bind group (slab) where the GPU data is located. pub group: MaterialBindGroupIndex, /// The slot within that bind group. + /// + /// Non-bindless materials will always have a slot of 0. pub slot: MaterialBindGroupSlot, } @@ -139,7 +260,7 @@ pub struct MaterialBindingId { /// /// In bindless mode, each bind group contains multiple materials. In /// non-bindless mode, each bind group contains only one material. -#[derive(Clone, Copy, Debug, Default, Reflect, PartialEq, Deref, DerefMut)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Reflect, Deref, DerefMut)] #[reflect(Default)] pub struct MaterialBindGroupIndex(pub u32); @@ -155,634 +276,1156 @@ impl From for MaterialBindGroupIndex { /// In bindless mode, this slot is needed to locate the material data in each /// bind group, since multiple materials are packed into a single slab. In /// non-bindless mode, this slot is always 0. -#[derive(Clone, Copy, Debug, Default, Reflect, Deref, DerefMut)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Reflect, Deref, DerefMut)] #[reflect(Default)] -pub struct MaterialBindGroupSlot(pub u16); +pub struct MaterialBindGroupSlot(pub u32); + +/// The CPU/GPU synchronization state of a buffer that we maintain. +/// +/// Currently, the only buffer that we maintain is the +/// [`MaterialBindlessIndexTable`]. +enum BufferDirtyState { + /// The buffer is currently synchronized between the CPU and GPU. + Clean, + /// The buffer hasn't been created yet. + NeedsReserve, + /// The buffer exists on both CPU and GPU, but the GPU data is out of date. + NeedsUpload, +} + +/// Information that describes a potential allocation of an +/// [`UnpreparedBindGroup`] into a slab. +struct BindlessAllocationCandidate { + /// A map that, for every resource in the [`UnpreparedBindGroup`] that + /// already existed in this slab, maps bindless index of that resource to + /// its slot in the appropriate binding array. + pre_existing_resources: HashMap, + /// Stores the number of free slots that are needed to satisfy this + /// allocation. + needed_free_slots: u32, +} + +/// A trait that allows fetching the [`BindingResourceId`] from a +/// [`BindlessResourceType`]. +/// +/// This is used when freeing bindless resources, in order to locate the IDs +/// assigned to each resource so that they can be removed from the appropriate +/// maps. +trait GetBindingResourceId { + /// Returns the [`BindingResourceId`] for this resource. + /// + /// `resource_type` specifies this resource's type. This is used for + /// textures, as a `wgpu` [`TextureView`] doesn't store enough information + /// itself to determine its dimension. + fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId; +} + +/// The public interface to a slab, which represents a single bind group. +pub struct MaterialSlab<'a, M>(MaterialSlabImpl<'a, M>) +where + M: Material; + +/// The actual implementation of a material slab. +/// +/// This has bindless and non-bindless variants. +enum MaterialSlabImpl<'a, M> +where + M: Material, +{ + /// The implementation of the slab interface we use when the slab + /// is bindless. + Bindless(&'a MaterialBindlessSlab), + /// The implementation of the slab interface we use when the slab + /// is non-bindless. + NonBindless(MaterialNonBindlessSlab<'a, M>), +} + +/// A single bind group that the [`MaterialBindGroupNonBindlessAllocator`] +/// manages. +enum MaterialNonBindlessSlab<'a, M> +where + M: Material, +{ + /// A slab that has a bind group. + Prepared(&'a PreparedBindGroup), + /// A slab that doesn't yet have a bind group. + Unprepared(&'a UnpreparedBindGroup), +} impl From for MaterialBindGroupSlot { fn from(value: u32) -> Self { - MaterialBindGroupSlot(value as u16) + MaterialBindGroupSlot(value) } } impl From for u32 { fn from(value: MaterialBindGroupSlot) -> Self { - value.0 as u32 + value.0 } } -/// A temporary data structure that contains references to bindless resources. -/// -/// We need this because the `wgpu` bindless API takes a slice of references. -/// Thus we need to create intermediate vectors of bindless resources in order -/// to satisfy the lifetime requirements. -enum BindingResourceArray<'a> { - Buffers(Vec>), - TextureViews(TextureViewDimension, Vec<&'a WgpuTextureView>), - Samplers(Vec<&'a WgpuSampler>), +impl<'a> From<&'a OwnedBindingResource> for BindingResourceId { + fn from(value: &'a OwnedBindingResource) -> Self { + match *value { + OwnedBindingResource::Buffer(ref buffer) => BindingResourceId::Buffer(buffer.id()), + OwnedBindingResource::TextureView(ref texture_view_dimension, ref texture_view) => { + BindingResourceId::TextureView(*texture_view_dimension, texture_view.id()) + } + OwnedBindingResource::Sampler(_, ref sampler) => { + BindingResourceId::Sampler(sampler.id()) + } + } + } } -/// Contains dummy resources that we use to pad out bindless arrays. -/// -/// On DX12, every binding array slot must be filled, so we have to fill unused -/// slots. -#[derive(Resource)] -pub struct FallbackBindlessResources { - /// A dummy sampler that we fill unused slots in bindless sampler arrays - /// with. - fallback_sampler: Sampler, +impl GetBindingResourceId for Buffer { + fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId { + BindingResourceId::Buffer(self.id()) + } } -struct MaterialFallbackBuffers(HashMap); +impl GetBindingResourceId for Sampler { + fn binding_resource_id(&self, _: BindlessResourceType) -> BindingResourceId { + BindingResourceId::Sampler(self.id()) + } +} -/// The minimum byte size of each fallback buffer. -const MIN_BUFFER_SIZE: u64 = 16; +impl GetBindingResourceId for TextureView { + fn binding_resource_id(&self, resource_type: BindlessResourceType) -> BindingResourceId { + let texture_view_dimension = match resource_type { + BindlessResourceType::Texture1d => TextureViewDimension::D1, + BindlessResourceType::Texture2d => TextureViewDimension::D2, + BindlessResourceType::Texture2dArray => TextureViewDimension::D2Array, + BindlessResourceType::Texture3d => TextureViewDimension::D3, + BindlessResourceType::TextureCube => TextureViewDimension::Cube, + BindlessResourceType::TextureCubeArray => TextureViewDimension::CubeArray, + _ => panic!("Resource type is not a texture"), + }; + BindingResourceId::TextureView(texture_view_dimension, self.id()) + } +} impl MaterialBindGroupAllocator where M: Material, { - /// Creates or recreates any bind groups that were modified this frame. - pub fn prepare_bind_groups( - &mut self, - render_device: &RenderDevice, - fallback_image: &FallbackImage, - fallback_resources: &FallbackBindlessResources, - ) { - for bind_group in &mut self.bind_groups { - bind_group.rebuild_bind_group_if_necessary( + /// Creates a new [`MaterialBindGroupAllocator`] managing the data for a + /// single material. + fn new(render_device: &RenderDevice) -> MaterialBindGroupAllocator { + if material_uses_bindless_resources::(render_device) { + MaterialBindGroupAllocator::Bindless(Box::new(MaterialBindGroupBindlessAllocator::new( render_device, - &self.bind_group_layout, - fallback_image, - fallback_resources, - &self.fallback_buffers, - ); + ))) + } else { + MaterialBindGroupAllocator::NonBindless(Box::new( + MaterialBindGroupNonBindlessAllocator::new(), + )) } } - /// Returns the bind group with the given index, if it exists. - #[inline] - pub fn get(&self, index: MaterialBindGroupIndex) -> Option<&MaterialBindGroup> { - self.bind_groups.get(index.0 as usize) - } - - /// Allocates a new binding slot and returns its ID. - pub fn allocate(&mut self) -> MaterialBindingId { - let group_index = self.free_bind_groups.pop().unwrap_or_else(|| { - let group_index = self.bind_groups.len() as u32; - self.bind_groups - .push(MaterialBindGroup::new(self.bindless_enabled)); - group_index - }); - - let bind_group = &mut self.bind_groups[group_index as usize]; - let slot_index = bind_group.allocate(); - - if !bind_group.is_full() { - self.free_bind_groups.push(group_index); - } - - MaterialBindingId { - group: group_index.into(), - slot: slot_index, + /// Returns the slab with the given index, if one exists. + pub fn get(&self, group: MaterialBindGroupIndex) -> Option> { + match *self { + MaterialBindGroupAllocator::Bindless(ref bindless_allocator) => bindless_allocator + .get(group) + .map(|bindless_slab| MaterialSlab(MaterialSlabImpl::Bindless(bindless_slab))), + MaterialBindGroupAllocator::NonBindless(ref non_bindless_allocator) => { + non_bindless_allocator.get(group).map(|non_bindless_slab| { + MaterialSlab(MaterialSlabImpl::NonBindless(non_bindless_slab)) + }) + } } } - /// Assigns an unprepared bind group to the group and slot specified in the - /// [`MaterialBindingId`]. - pub fn init( + /// Allocates an [`UnpreparedBindGroup`] and returns the resulting binding ID. + /// + /// This method should generally be preferred over + /// [`Self::allocate_prepared`], because this method supports both bindless + /// and non-bindless bind groups. Only use [`Self::allocate_prepared`] if + /// you need to prepare the bind group yourself. + pub fn allocate_unprepared( &mut self, - render_device: &RenderDevice, - material_binding_id: MaterialBindingId, unprepared_bind_group: UnpreparedBindGroup, - ) { - self.bind_groups[material_binding_id.group.0 as usize].init( - render_device, - &self.bind_group_layout, - material_binding_id.slot, - unprepared_bind_group, - ); + bind_group_layout: &BindGroupLayout, + ) -> MaterialBindingId { + match *self { + MaterialBindGroupAllocator::Bindless( + ref mut material_bind_group_bindless_allocator, + ) => material_bind_group_bindless_allocator.allocate_unprepared(unprepared_bind_group), + MaterialBindGroupAllocator::NonBindless( + ref mut material_bind_group_non_bindless_allocator, + ) => material_bind_group_non_bindless_allocator + .allocate_unprepared(unprepared_bind_group, (*bind_group_layout).clone()), + } } - /// Fills a slot directly with a custom bind group. + /// Places a pre-prepared bind group into a slab. + /// + /// For bindless materials, the allocator internally manages the bind + /// groups, so calling this method will panic if this is a bindless + /// allocator. Only non-bindless allocators support this method. /// - /// This is only a meaningful operation for non-bindless bind groups. It's - /// rarely used, but see the `texture_binding_array` example for an example - /// demonstrating how this feature might see use in practice. - pub fn init_custom( + /// It's generally preferred to use [`Self::allocate_unprepared`], because + /// that method supports both bindless and non-bindless allocators. Only use + /// this method if you need to prepare the bind group yourself. + pub fn allocate_prepared( &mut self, - material_binding_id: MaterialBindingId, - bind_group: BindGroup, - bind_group_data: M::Data, - ) { - self.bind_groups[material_binding_id.group.0 as usize] - .init_custom(bind_group, bind_group_data); + prepared_bind_group: PreparedBindGroup, + ) -> MaterialBindingId { + match *self { + MaterialBindGroupAllocator::Bindless(_) => { + panic!( + "Bindless resources are incompatible with implementing `as_bind_group` \ + directly; implement `unprepared_bind_group` instead or disable bindless" + ) + } + MaterialBindGroupAllocator::NonBindless(ref mut non_bindless_allocator) => { + non_bindless_allocator.allocate_prepared(prepared_bind_group) + } + } } - /// Marks the slot corresponding to the given [`MaterialBindingId`] as free. + /// Deallocates the material with the given binding ID. + /// + /// Any resources that are no longer referenced are removed from the slab. pub fn free(&mut self, material_binding_id: MaterialBindingId) { - let bind_group = &mut self.bind_groups[material_binding_id.group.0 as usize]; - let was_full = bind_group.is_full(); + match *self { + MaterialBindGroupAllocator::Bindless( + ref mut material_bind_group_bindless_allocator, + ) => material_bind_group_bindless_allocator.free(material_binding_id), + MaterialBindGroupAllocator::NonBindless( + ref mut material_bind_group_non_bindless_allocator, + ) => material_bind_group_non_bindless_allocator.free(material_binding_id), + } + } - bind_group.free(material_binding_id.slot); + /// Recreates any bind groups corresponding to slabs that have been modified + /// since last calling [`MaterialBindGroupAllocator::prepare_bind_groups`]. + pub fn prepare_bind_groups( + &mut self, + render_device: &RenderDevice, + fallback_bindless_resources: &FallbackBindlessResources, + fallback_image: &FallbackImage, + ) { + match *self { + MaterialBindGroupAllocator::Bindless( + ref mut material_bind_group_bindless_allocator, + ) => material_bind_group_bindless_allocator.prepare_bind_groups( + render_device, + fallback_bindless_resources, + fallback_image, + ), + MaterialBindGroupAllocator::NonBindless( + ref mut material_bind_group_non_bindless_allocator, + ) => material_bind_group_non_bindless_allocator.prepare_bind_groups(render_device), + } + } - // If the group that this material belonged to was full, it now contains - // at least one free slot, so add the group to the `free_bind_groups` - // list. - if was_full { - debug_assert!(!self.free_bind_groups.contains(&material_binding_id.group.0)); - self.free_bind_groups.push(*material_binding_id.group); + /// Uploads the contents of all buffers that this + /// [`MaterialBindGroupAllocator`] manages to the GPU. + /// + /// Non-bindless allocators don't currently manage any buffers, so this + /// method only has an effect for bindless allocators. + pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { + match *self { + MaterialBindGroupAllocator::Bindless( + ref mut material_bind_group_bindless_allocator, + ) => material_bind_group_bindless_allocator.write_buffers(render_device, render_queue), + MaterialBindGroupAllocator::NonBindless(_) => { + // Not applicable. + } } } } -impl MaterialBindGroup +impl MaterialBindlessIndexTable where M: Material, { - /// Creates a new material bind group. - fn new(bindless: bool) -> MaterialBindGroup { - if bindless { - MaterialBindGroup::Bindless(MaterialBindlessBindGroup::new()) - } else { - MaterialBindGroup::NonBindless(MaterialNonBindlessBindGroup::new()) + /// Creates a new [`MaterialBindlessIndexTable`] for a single slab. + fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessIndexTable { + // Preallocate space for one bindings table, so that there will always be a buffer. + let mut buffer = RawBufferVec::new(BufferUsages::STORAGE); + for _ in 0..bindless_descriptor.resources.len() { + buffer.push(0); } - } - /// Allocates a new binding slot and returns its ID. - fn allocate(&mut self) -> MaterialBindGroupSlot { - match *self { - MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => { - material_bindless_bind_group.allocate() - } - MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => { - material_non_bindless_bind_group.allocate() - } + MaterialBindlessIndexTable { + buffer, + buffer_dirty: BufferDirtyState::NeedsReserve, + phantom: PhantomData, } } - /// Assigns an unprepared bind group to the group and slot specified in the - /// [`MaterialBindingId`]. - fn init( + /// Returns the binding index table for a single material. + /// + /// 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(); + let start = struct_size * slot.0 as usize; + &self.buffer.values()[start..(start + struct_size)] + } + + /// Updates the binding index table for a single material. + /// + /// The `allocated_resource_slots` map contains a mapping from the + /// [`BindlessIndex`] of each resource that the material references to the + /// slot that that resource occupies in the appropriate binding array. This + /// method serializes that map into a binding index table that the shader + /// can read. + fn set( &mut self, - render_device: &RenderDevice, - bind_group_layout: &BindGroupLayout, slot: MaterialBindGroupSlot, - unprepared_bind_group: UnpreparedBindGroup, + allocated_resource_slots: &HashMap, + bindless_descriptor: &BindlessDescriptor, ) { - match *self { - MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => { - material_bindless_bind_group.init( - render_device, - bind_group_layout, - slot, - unprepared_bind_group, - ); - } - MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => { - material_non_bindless_bind_group.init( - render_device, - bind_group_layout, - slot, - unprepared_bind_group, - ); - } + let table_len = bindless_descriptor.resources.len(); + 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); + } + + // Mark the buffer as needing to be recreated, in case we grew it. + self.buffer_dirty = BufferDirtyState::NeedsReserve; } - /// Fills a slot directly with a custom bind group. - /// - /// This is only a meaningful operation for non-bindless bind groups. It's - /// rarely used, but see the `texture_binding_array` example for an example - /// demonstrating how this feature might see use in practice. - fn init_custom(&mut self, bind_group: BindGroup, extra_data: M::Data) { - match *self { - MaterialBindGroup::Bindless(_) => { - error!("Custom bind groups aren't supported in bindless mode"); - } - MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => { - material_non_bindless_bind_group.init_custom(bind_group, extra_data); + /// Creates the buffer that contains the bindless index table if necessary. + fn prepare_buffer(&mut self, render_device: &RenderDevice) { + match self.buffer_dirty { + BufferDirtyState::Clean | BufferDirtyState::NeedsUpload => {} + BufferDirtyState::NeedsReserve => { + let capacity = self.buffer.len(); + self.buffer.reserve(capacity, render_device); + self.buffer_dirty = BufferDirtyState::NeedsUpload; } } } - /// Marks the slot corresponding to the given [`MaterialBindGroupSlot`] as - /// free. - fn free(&mut self, material_bind_group_slot: MaterialBindGroupSlot) { - match *self { - MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => { - material_bindless_bind_group.free(material_bind_group_slot); - } - MaterialBindGroup::NonBindless(ref mut material_non_bindless_bind_group) => { - material_non_bindless_bind_group.free(material_bind_group_slot); + /// Writes the contents of the bindless index table buffer to GPU if + /// necessary. + fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { + match self.buffer_dirty { + BufferDirtyState::Clean => {} + BufferDirtyState::NeedsReserve | BufferDirtyState::NeedsUpload => { + self.buffer.write_buffer(render_device, render_queue); + self.buffer_dirty = BufferDirtyState::Clean; } } } +} - /// Returns the actual bind group, or `None` if it hasn't been created yet. - pub fn get_bind_group(&self) -> Option<&BindGroup> { - match *self { - MaterialBindGroup::Bindless(ref material_bindless_bind_group) => { - material_bindless_bind_group.get_bind_group() - } - MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => { - material_non_bindless_bind_group.get_bind_group() - } +impl MaterialBindGroupBindlessAllocator +where + M: Material, +{ + /// Creates a new [`MaterialBindGroupBindlessAllocator`] managing the data + /// for a single bindless material. + fn new(render_device: &RenderDevice) -> MaterialBindGroupBindlessAllocator { + let bindless_descriptor = M::bindless_descriptor() + .expect("Non-bindless materials should use the non-bindless allocator"); + let fallback_buffers = bindless_descriptor + .buffers + .iter() + .map(|bindless_buffer_descriptor| { + ( + bindless_buffer_descriptor.bindless_index, + render_device.create_buffer(&BufferDescriptor { + label: Some("bindless fallback buffer"), + size: bindless_buffer_descriptor.size as u64, + usage: BufferUsages::STORAGE, + mapped_at_creation: false, + }), + ) + }) + .collect(); + + MaterialBindGroupBindlessAllocator { + slabs: vec![], + bind_group_layout: M::bind_group_layout(render_device), + bindless_descriptor, + fallback_buffers, + slab_capacity: M::bindless_slot_count() + .expect("Non-bindless materials should use the non-bindless allocator") + .resolve(), } } - /// Returns true if all the slots are full or false if at least one slot in - /// this bind group is free. - fn is_full(&self) -> bool { - match *self { - MaterialBindGroup::Bindless(ref material_bindless_bind_group) => { - material_bindless_bind_group.is_full() - } - MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => { - material_non_bindless_bind_group.is_full() + /// Allocates the resources for a single material into a slab and returns + /// the resulting ID. + /// + /// The returned [`MaterialBindingId`] can later be used to fetch the slab + /// that was used. + /// + /// This function can't fail. If all slabs are full, then a new slab is + /// created, and the material is allocated into it. + fn allocate_unprepared( + &mut self, + mut unprepared_bind_group: UnpreparedBindGroup, + ) -> 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, + ) { + Ok(slot) => { + return MaterialBindingId { + group: MaterialBindGroupIndex(slab_index as u32), + slot, + }; + } + Err(bind_group) => unprepared_bind_group = bind_group, } } + + let group = MaterialBindGroupIndex(self.slabs.len() as u32); + self.slabs + .push(MaterialBindlessSlab::new(&self.bindless_descriptor)); + + // Allocate into the newly-pushed slab. + let Ok(slot) = self + .slabs + .last_mut() + .expect("We just pushed a slab") + .try_allocate( + unprepared_bind_group, + &self.bindless_descriptor, + self.slab_capacity, + ) + else { + panic!("An allocation into an empty slab should always succeed") + }; + + MaterialBindingId { group, slot } } - /// Recreates the bind group for this material bind group containing the - /// data for every material in it. - fn rebuild_bind_group_if_necessary( + /// Deallocates the material with the given binding ID. + /// + /// Any resources that are no longer referenced are removed from the slab. + fn free(&mut self, material_binding_id: MaterialBindingId) { + self.slabs + .get_mut(material_binding_id.group.0 as usize) + .expect("Slab should exist") + .free(material_binding_id.slot, &self.bindless_descriptor); + } + + /// Returns the slab with the given bind group index. + /// + /// A [`MaterialBindGroupIndex`] can be fetched from a + /// [`MaterialBindingId`]. + fn get(&self, group: MaterialBindGroupIndex) -> Option<&MaterialBindlessSlab> { + self.slabs.get(group.0 as usize) + } + + /// Recreates any bind groups corresponding to slabs that have been modified + /// since last calling + /// [`MaterialBindGroupBindlessAllocator::prepare_bind_groups`]. + fn prepare_bind_groups( &mut self, render_device: &RenderDevice, - bind_group_layout: &BindGroupLayout, - fallback_image: &FallbackImage, fallback_bindless_resources: &FallbackBindlessResources, - fallback_buffers: &MaterialFallbackBuffers, + fallback_image: &FallbackImage, ) { - match *self { - MaterialBindGroup::Bindless(ref mut material_bindless_bind_group) => { - material_bindless_bind_group.rebuild_bind_group_if_necessary( - render_device, - bind_group_layout, - fallback_image, - fallback_bindless_resources, - fallback_buffers, - ); - } - MaterialBindGroup::NonBindless(_) => {} + for slab in &mut self.slabs { + slab.prepare( + render_device, + &self.bind_group_layout, + fallback_bindless_resources, + &self.fallback_buffers, + fallback_image, + &self.bindless_descriptor, + ); } } - /// Returns the associated extra data for the material with the given slot. - pub fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data { - match *self { - MaterialBindGroup::Bindless(ref material_bindless_bind_group) => { - material_bindless_bind_group.get_extra_data(slot) - } - MaterialBindGroup::NonBindless(ref material_non_bindless_bind_group) => { - material_non_bindless_bind_group.get_extra_data(slot) - } + /// Writes any buffers that we're managing to the GPU. + /// + /// Currently, this only consists of the bindless index tables. + fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { + for slab in &mut self.slabs { + slab.write_buffer(render_device, render_queue); } } } -impl MaterialBindlessBindGroup +impl FromWorld for MaterialBindGroupAllocator where M: Material, { - /// Returns a new bind group. - fn new() -> MaterialBindlessBindGroup { - let count = M::bindless_slot_count().unwrap_or(1); + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + MaterialBindGroupAllocator::new(render_device) + } +} - MaterialBindlessBindGroup { - bind_group: None, - unprepared_bind_groups: iter::repeat_with(|| None).take(count as usize).collect(), - used_slot_bitmap: 0, +impl MaterialBindlessSlab +where + M: Material, +{ + /// Attempts to allocate the given unprepared bind group in this slab. + /// + /// If the allocation succeeds, this method returns the slot that the + /// allocation was placed in. If the allocation fails because the slab was + /// full, this method returns the unprepared bind group back to the caller + /// so that it can try to allocate again. + 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. + let Some(allocation_candidate) = self.check_allocation(&unprepared_bind_group) else { + return Err(unprepared_bind_group); + }; + + // Check to see if we have enough free space. + // + // As a special case, note that if *nothing* is allocated in this slab, + // then we always allow a material to be placed in it, regardless of the + // number of bindings the material has. This is so that, if the + // platform's maximum bindless count is set too low to hold even a + // single material, we can still place each material into a separate + // slab instead of failing outright. + if self.allocated_resource_count > 0 + && self.allocated_resource_count + allocation_candidate.needed_free_slots + > slot_capacity + { + trace!("Slab is full, can't allocate"); + return Err(unprepared_bind_group); + } + + // OK, we can allocate in this slab. Assign a slot ID. + let slot = self + .free_slots + .pop() + .unwrap_or(MaterialBindGroupSlot(self.live_allocation_count)); + + // Bump the live allocation count. + self.live_allocation_count += 1; + + // Insert the resources into the binding arrays. + let allocated_resource_slots = + 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); + + // Insert extra data. + if self.extra_data.len() < (*slot as usize + 1) { + self.extra_data.resize_with(*slot as usize + 1, || None); } + self.extra_data[*slot as usize] = Some(unprepared_bind_group.data); + + // Invalidate the cached bind group. + self.bind_group = None; + + Ok(slot) } - /// Allocates a new slot and returns its index. - /// - /// This bind group must not be full. - fn allocate(&mut self) -> MaterialBindGroupSlot { - debug_assert!(!self.is_full()); + /// Gathers the information needed to determine whether the given unprepared + /// bind group can be allocated in this slab. + fn check_allocation( + &self, + unprepared_bind_group: &UnpreparedBindGroup, + ) -> Option { + let mut allocation_candidate = BindlessAllocationCandidate { + pre_existing_resources: HashMap::default(), + needed_free_slots: 0, + }; - // Mark the slot as used. - let slot = self.used_slot_bitmap.trailing_ones(); - self.used_slot_bitmap |= 1 << slot; + for &(bindless_index, ref owned_binding_resource) in unprepared_bind_group.bindings.iter() { + let bindless_index = BindlessIndex(bindless_index); + match *owned_binding_resource { + OwnedBindingResource::Buffer(ref buffer) => { + let Some(binding_array) = self.buffers.get(&bindless_index) else { + error!( + "Binding array wasn't present for buffer at index {:?}", + bindless_index + ); + return None; + }; + match binding_array.find(BindingResourceId::Buffer(buffer.id())) { + Some(slot) => { + allocation_candidate + .pre_existing_resources + .insert(bindless_index, slot); + } + None => allocation_candidate.needed_free_slots += 1, + } + } - slot.into() + OwnedBindingResource::TextureView(texture_view_dimension, ref texture_view) => { + let bindless_resource_type = BindlessResourceType::from(texture_view_dimension); + match self + .textures + .get(&bindless_resource_type) + .expect("Missing binding array for texture") + .find(BindingResourceId::TextureView( + texture_view_dimension, + texture_view.id(), + )) { + Some(slot) => { + allocation_candidate + .pre_existing_resources + .insert(bindless_index, slot); + } + None => { + allocation_candidate.needed_free_slots += 1; + } + } + } + + OwnedBindingResource::Sampler(sampler_binding_type, ref sampler) => { + let bindless_resource_type = BindlessResourceType::from(sampler_binding_type); + match self + .samplers + .get(&bindless_resource_type) + .expect("Missing binding array for sampler") + .find(BindingResourceId::Sampler(sampler.id())) + { + Some(slot) => { + allocation_candidate + .pre_existing_resources + .insert(bindless_index, slot); + } + None => { + allocation_candidate.needed_free_slots += 1; + } + } + } + } + } + + Some(allocation_candidate) } - /// Assigns the given unprepared bind group to the given slot. - fn init( + /// Inserts the given [`BindingResources`] into this slab. + /// + /// Returns a table that maps the bindless index of each resource to its + /// slot in its binding array. + fn insert_resources( &mut self, - _: &RenderDevice, - _: &BindGroupLayout, - slot: MaterialBindGroupSlot, - unprepared_bind_group: UnpreparedBindGroup, - ) { - self.unprepared_bind_groups[slot.0 as usize] = Some(unprepared_bind_group); + mut binding_resources: BindingResources, + allocation_candidate: BindlessAllocationCandidate, + ) -> HashMap { + let mut allocated_resource_slots = HashMap::default(); + + for (bindless_index, owned_binding_resource) in binding_resources.drain(..) { + let bindless_index = BindlessIndex(bindless_index); + // If this is an other reference to an object we've already + // allocated, just bump its reference count. + if let Some(pre_existing_resource_slot) = allocation_candidate + .pre_existing_resources + .get(&bindless_index) + { + allocated_resource_slots.insert(bindless_index, *pre_existing_resource_slot); + + match owned_binding_resource { + OwnedBindingResource::Buffer(_) => { + self.buffers + .get_mut(&bindless_index) + .expect("Buffer binding array should exist") + .bindings + .get_mut(*pre_existing_resource_slot as usize) + .and_then(|binding| binding.as_mut()) + .expect("Slot should exist") + .ref_count += 1; + } + OwnedBindingResource::TextureView(texture_view_dimension, _) => { + let bindless_resource_type = + BindlessResourceType::from(texture_view_dimension); + self.textures + .get_mut(&bindless_resource_type) + .expect("Texture binding array should exist") + .bindings + .get_mut(*pre_existing_resource_slot as usize) + .and_then(|binding| binding.as_mut()) + .expect("Slot should exist") + .ref_count += 1; + } + OwnedBindingResource::Sampler(sampler_binding_type, _) => { + let bindless_resource_type = + BindlessResourceType::from(sampler_binding_type); + self.samplers + .get_mut(&bindless_resource_type) + .expect("Sampler binding array should exist") + .bindings + .get_mut(*pre_existing_resource_slot as usize) + .and_then(|binding| binding.as_mut()) + .expect("Slot should exist") + .ref_count += 1; + } + } - // Invalidate the cached bind group so that we rebuild it again. - self.bind_group = None; + continue; + } + + // Otherwise, we need to insert it anew. + let binding_resource_id = BindingResourceId::from(&owned_binding_resource); + match owned_binding_resource { + OwnedBindingResource::Buffer(buffer) => { + let slot = self + .buffers + .get_mut(&bindless_index) + .expect("Buffer binding array should exist") + .insert(binding_resource_id, buffer); + allocated_resource_slots.insert(bindless_index, slot); + } + OwnedBindingResource::TextureView(texture_view_dimension, texture_view) => { + let bindless_resource_type = BindlessResourceType::from(texture_view_dimension); + let slot = self + .textures + .get_mut(&bindless_resource_type) + .expect("Texture array should exist") + .insert(binding_resource_id, texture_view); + allocated_resource_slots.insert(bindless_index, slot); + } + OwnedBindingResource::Sampler(sampler_binding_type, sampler) => { + let bindless_resource_type = BindlessResourceType::from(sampler_binding_type); + let slot = self + .samplers + .get_mut(&bindless_resource_type) + .expect("Sampler should exist") + .insert(binding_resource_id, sampler); + allocated_resource_slots.insert(bindless_index, slot); + } + } + + // Bump the allocated resource count. + self.allocated_resource_count += 1; + } + + allocated_resource_slots } - /// Marks the given slot as free. - fn free(&mut self, slot: MaterialBindGroupSlot) { - self.unprepared_bind_groups[slot.0 as usize] = None; - self.used_slot_bitmap &= !(1 << slot.0); + /// Removes the material allocated in the given slot, with the given + /// 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() + { + let bindless_index = BindlessIndex::from(bindless_index as u32); + + // Free the binding. + let resource_freed = match *bindless_resource_type { + BindlessResourceType::None => false, + BindlessResourceType::Buffer => self + .buffers + .get_mut(&bindless_index) + .expect("Buffer should exist with that bindless index") + .remove(bindless_binding), + BindlessResourceType::SamplerFiltering + | BindlessResourceType::SamplerNonFiltering + | BindlessResourceType::SamplerComparison => self + .samplers + .get_mut(bindless_resource_type) + .expect("Sampler array should exist") + .remove(bindless_binding), + BindlessResourceType::Texture1d + | BindlessResourceType::Texture2d + | BindlessResourceType::Texture2dArray + | BindlessResourceType::Texture3d + | BindlessResourceType::TextureCube + | BindlessResourceType::TextureCubeArray => self + .textures + .get_mut(bindless_resource_type) + .expect("Texture array should exist") + .remove(bindless_binding), + }; - // Invalidate the cached bind group so that we rebuild it again. + // If the slot is now free, decrement the allocated resource + // count. + if resource_freed { + self.allocated_resource_count -= 1; + } + } + + // Clear out the extra data. + self.extra_data[slot.0 as usize] = None; + + // Invalidate the cached bind group. self.bind_group = None; - } - /// Returns true if all the slots are full or false if at least one slot in - /// this bind group is free. - fn is_full(&self) -> bool { - self.used_slot_bitmap == (1 << (self.unprepared_bind_groups.len() as u32)) - 1 + // Release the slot ID. + self.free_slots.push(slot); + self.live_allocation_count -= 1; } - /// Returns the actual bind group, or `None` if it hasn't been created yet. - fn get_bind_group(&self) -> Option<&BindGroup> { - self.bind_group.as_ref() + /// Recreates the bind group and bindless index table buffer if necessary. + fn prepare( + &mut self, + render_device: &RenderDevice, + bind_group_layout: &BindGroupLayout, + fallback_bindless_resources: &FallbackBindlessResources, + fallback_buffers: &HashMap, + fallback_image: &FallbackImage, + bindless_descriptor: &BindlessDescriptor, + ) { + // Create the bindless index table buffer if needed. + self.bindless_index_table.prepare_buffer(render_device); + + // Create the bind group if needed. + self.prepare_bind_group( + render_device, + bind_group_layout, + fallback_bindless_resources, + fallback_buffers, + fallback_image, + bindless_descriptor, + ); } - /// Recreates the bind group for this material bind group containing the - /// data for every material in it. - fn rebuild_bind_group_if_necessary( + /// Recreates the bind group if this slab has been changed since the last + /// time we created it. + fn prepare_bind_group( &mut self, render_device: &RenderDevice, bind_group_layout: &BindGroupLayout, - fallback_image: &FallbackImage, fallback_bindless_resources: &FallbackBindlessResources, - fallback_buffers: &MaterialFallbackBuffers, + fallback_buffers: &HashMap, + fallback_image: &FallbackImage, + bindless_descriptor: &BindlessDescriptor, ) { + // If the bind group is clean, then do nothing. if self.bind_group.is_some() { return; } - let Some(first_bind_group) = self - .unprepared_bind_groups - .iter() - .find_map(|slot| slot.as_ref()) - else { - return; - }; - - // Creates the intermediate binding resource vectors. - let Some(binding_resource_arrays) = self.recreate_binding_resource_arrays( - first_bind_group, - fallback_image, + let binding_resource_arrays = self.create_binding_resource_arrays( fallback_bindless_resources, fallback_buffers, - ) else { - return; - }; + fallback_image, + bindless_descriptor, + ); - // Now build the actual resource arrays for `wgpu`. - let entries = binding_resource_arrays - .iter() - .map(|&(&binding, ref binding_resource_array)| BindGroupEntry { + let mut bind_group_entries = vec![BindGroupEntry { + binding: 0, + resource: self + .bindless_index_table + .buffer + .buffer() + .expect("Bindings buffer must exist") + .as_entire_binding(), + }]; + + for &(&binding, ref binding_resource_array) in binding_resource_arrays.iter() { + bind_group_entries.push(BindGroupEntry { binding, resource: match *binding_resource_array { - BindingResourceArray::Buffers(ref vec) => { - BindingResource::BufferArray(&vec[..]) + BindingResourceArray::Buffers(ref buffer_bindings) => { + BindingResource::BufferArray(&buffer_bindings[..]) } - BindingResourceArray::TextureViews(_, ref vec) => { - BindingResource::TextureViewArray(&vec[..]) + BindingResourceArray::TextureViews(ref texture_views) => { + BindingResource::TextureViewArray(&texture_views[..]) } - BindingResourceArray::Samplers(ref vec) => { - BindingResource::SamplerArray(&vec[..]) + BindingResourceArray::Samplers(ref samplers) => { + BindingResource::SamplerArray(&samplers[..]) } }, - }) - .collect::>(); + }); + } - self.bind_group = - Some(render_device.create_bind_group(M::label(), bind_group_layout, &entries)); + self.bind_group = Some(render_device.create_bind_group( + M::label(), + bind_group_layout, + &bind_group_entries, + )); + } + + /// Writes any buffers that we're managing to the GPU. + /// + /// Currently, this only consists of the bindless index table. + fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { + self.bindless_index_table + .write_buffer(render_device, render_queue); } - /// Recreates the binding arrays for each material in this bind group. - fn recreate_binding_resource_arrays<'a>( + /// Converts our binding arrays into binding resource arrays suitable for + /// passing to `wgpu`. + fn create_binding_resource_arrays<'a>( &'a self, - first_bind_group: &'a UnpreparedBindGroup, + fallback_bindless_resources: &'a FallbackBindlessResources, + fallback_buffers: &'a HashMap, fallback_image: &'a FallbackImage, + bindless_descriptor: &'a BindlessDescriptor, + ) -> Vec<(&'a u32, BindingResourceArray<'a>)> { + let mut binding_resource_arrays = vec![]; + + // Build sampler bindings. + self.create_sampler_binding_resource_arrays( + &mut binding_resource_arrays, + fallback_bindless_resources, + ); + + // Build texture bindings. + self.create_texture_binding_resource_arrays(&mut binding_resource_arrays, fallback_image); + + // Build buffer bindings. + self.create_buffer_binding_resource_arrays( + &mut binding_resource_arrays, + fallback_buffers, + bindless_descriptor, + ); + + binding_resource_arrays + } + + /// Accumulates sampler binding arrays into binding resource arrays suitable + /// for passing to `wgpu`. + fn create_sampler_binding_resource_arrays<'a, 'b>( + &'a self, + binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>, fallback_bindless_resources: &'a FallbackBindlessResources, - fallback_buffers: &'a MaterialFallbackBuffers, - ) -> Option)>> { - // Initialize the arrays. - let mut binding_resource_arrays = first_bind_group - .bindings - .iter() - .map(|(index, binding)| match *binding { - OwnedBindingResource::Buffer(..) => (index, BindingResourceArray::Buffers(vec![])), - OwnedBindingResource::TextureView(dimension, _) => { - (index, BindingResourceArray::TextureViews(dimension, vec![])) - } - OwnedBindingResource::Sampler(..) => { - (index, BindingResourceArray::Samplers(vec![])) + ) { + // We have one binding resource array per sampler type. + for (bindless_resource_type, fallback_sampler) in [ + ( + BindlessResourceType::SamplerFiltering, + &fallback_bindless_resources.filtering_sampler, + ), + ( + BindlessResourceType::SamplerNonFiltering, + &fallback_bindless_resources.non_filtering_sampler, + ), + ( + BindlessResourceType::SamplerComparison, + &fallback_bindless_resources.comparison_sampler, + ), + ] { + match self.samplers.get(&bindless_resource_type) { + Some(sampler_bindless_binding_array) => { + let sampler_bindings = sampler_bindless_binding_array + .bindings + .iter() + .map(|maybe_bindless_binding| match *maybe_bindless_binding { + Some(ref bindless_binding) => &bindless_binding.resource, + None => &**fallback_sampler, + }) + .collect(); + binding_resource_arrays.push(( + &*sampler_bindless_binding_array.binding_number, + BindingResourceArray::Samplers(sampler_bindings), + )); } - }) - .collect::>(); - for maybe_unprepared_bind_group in self.unprepared_bind_groups.iter() { - match *maybe_unprepared_bind_group { None => { - // Push dummy resources for this slot. - for binding_resource_array in &mut binding_resource_arrays { - match *binding_resource_array { - (binding, BindingResourceArray::Buffers(ref mut vec)) => { - vec.push(BufferBinding { - buffer: &fallback_buffers.0[binding], - offset: 0, - size: None, - }); - } - ( - _, - BindingResourceArray::TextureViews(texture_dimension, ref mut vec), - ) => vec.push(&fallback_image.get(texture_dimension).texture_view), - (_, BindingResourceArray::Samplers(ref mut vec)) => { - vec.push(&fallback_bindless_resources.fallback_sampler); - } - } - } + // Fill with a single fallback sampler. + let binding_number = bindless_resource_type + .binding_number() + .expect("Sampler bindless resource type must have a binding number"); + + binding_resource_arrays.push(( + &**binding_number, + BindingResourceArray::Samplers(vec![&**fallback_sampler]), + )); } + } + } + } - Some(ref unprepared_bind_group) => { - // Push the resources for this slot. - // - // All materials in this group must have the same type of - // binding (buffer, texture view, sampler) in each bind - // group entry. - for ( - binding_index, - (&mut (binding, ref mut binding_resource_array), (_, binding_resource)), - ) in binding_resource_arrays - .iter_mut() - .zip(unprepared_bind_group.bindings.0.iter()) - .enumerate() - { - match (binding_resource_array, binding_resource) { - ( - &mut BindingResourceArray::Buffers(ref mut vec), - OwnedBindingResource::Buffer(buffer), - ) => match NonZero::new(buffer.size()) { - None => vec.push(BufferBinding { - buffer: &fallback_buffers.0[binding], - offset: 0, - size: None, - }), - Some(size) => vec.push(BufferBinding { - buffer, - offset: 0, - size: Some(size), - }), - }, - ( - &mut BindingResourceArray::TextureViews(_, ref mut vec), - OwnedBindingResource::TextureView(_, texture_view), - ) => vec.push(texture_view), - ( - &mut BindingResourceArray::Samplers(ref mut vec), - OwnedBindingResource::Sampler(sampler), - ) => vec.push(sampler), - _ => { - error!( - "Mismatched bind group layouts for material \ - {} at bind group {}; can't combine bind \ - groups into a single bindless bind group!", - any::type_name::(), - binding_index, - ); - return None; - } - } - } + /// Accumulates texture binding arrays into binding resource arrays suitable + /// for passing to `wgpu`. + fn create_texture_binding_resource_arrays<'a, 'b>( + &'a self, + binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>, + fallback_image: &'a FallbackImage, + ) { + for (bindless_resource_type, fallback_image) in [ + (BindlessResourceType::Texture1d, &fallback_image.d1), + (BindlessResourceType::Texture2d, &fallback_image.d2), + ( + BindlessResourceType::Texture2dArray, + &fallback_image.d2_array, + ), + (BindlessResourceType::Texture3d, &fallback_image.d3), + (BindlessResourceType::TextureCube, &fallback_image.cube), + ( + BindlessResourceType::TextureCubeArray, + &fallback_image.cube_array, + ), + ] { + match self.textures.get(&bindless_resource_type) { + Some(texture_bindless_binding_array) => { + let texture_bindings = texture_bindless_binding_array + .bindings + .iter() + .map(|maybe_bindless_binding| match *maybe_bindless_binding { + Some(ref bindless_binding) => &*bindless_binding.resource, + None => &*fallback_image.texture_view, + }) + .collect(); + binding_resource_arrays.push(( + &*texture_bindless_binding_array.binding_number, + BindingResourceArray::TextureViews(texture_bindings), + )); + } + + None => { + // Fill with a single fallback image. + let binding_number = bindless_resource_type + .binding_number() + .expect("Texture bindless resource type must have a binding number"); + + binding_resource_arrays.push(( + binding_number, + BindingResourceArray::TextureViews(vec![&*fallback_image.texture_view]), + )); } } } + } + + /// Accumulates buffer binding arrays into binding resource arrays suitable + /// for `wgpu`. + fn create_buffer_binding_resource_arrays<'a, 'b>( + &'a self, + binding_resource_arrays: &'b mut Vec<(&'a u32, BindingResourceArray<'a>)>, + fallback_buffers: &'a HashMap, + bindless_descriptor: &'a BindlessDescriptor, + ) { + for bindless_buffer_descriptor in bindless_descriptor.buffers.iter() { + let Some(buffer_bindless_binding_array) = + self.buffers.get(&bindless_buffer_descriptor.bindless_index) + else { + error!( + "Slab didn't contain a binding array for buffer binding {:?}, bindless {:?}", + bindless_buffer_descriptor.binding_number, + bindless_buffer_descriptor.bindless_index, + ); + continue; + }; + let buffer_bindings = buffer_bindless_binding_array + .bindings + .iter() + .map(|maybe_bindless_binding| { + let buffer = match *maybe_bindless_binding { + None => fallback_buffers + .get(&bindless_buffer_descriptor.bindless_index) + .expect("Fallback buffer should exist"), + Some(ref bindless_binding) => &bindless_binding.resource, + }; + BufferBinding { + buffer, + offset: 0, + size: None, + } + }) + .collect(); + binding_resource_arrays.push(( + &*buffer_bindless_binding_array.binding_number, + BindingResourceArray::Buffers(buffer_bindings), + )); + } + } - Some(binding_resource_arrays) + /// Returns the [`BindGroup`] corresponding to this slab, if it's been + /// prepared. + fn bind_group(&self) -> Option<&BindGroup> { + self.bind_group.as_ref() } - /// Returns the associated extra data for the material with the given slot. + /// Returns the extra data associated with this material. fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data { - &self.unprepared_bind_groups[slot.0 as usize] - .as_ref() - .unwrap() - .data + self.extra_data + .get(slot.0 as usize) + .and_then(|data| data.as_ref()) + .expect("Extra data not present") } } -impl MaterialNonBindlessBindGroup +impl MaterialBindlessBindingArray where - M: Material, + R: GetBindingResourceId, { - /// Creates a new material bind group. - fn new() -> MaterialNonBindlessBindGroup { - MaterialNonBindlessBindGroup { - allocation: MaterialNonBindlessBindGroupAllocation::Unallocated, + /// Creates a new [`MaterialBindlessBindingArray`] with the given binding + /// number, managing resources of the given type. + fn new( + binding_number: BindingNumber, + resource_type: BindlessResourceType, + ) -> MaterialBindlessBindingArray { + MaterialBindlessBindingArray { + binding_number, + bindings: vec![], + resource_type, + resource_to_slot: HashMap::default(), + free_slots: vec![], + len: 0, } } - /// Allocates a new slot and returns its index. + /// Returns the slot corresponding to the given resource, if that resource + /// is located in this binding array. /// - /// This bind group must not be full. - fn allocate(&mut self) -> MaterialBindGroupSlot { - debug_assert!(!self.is_full()); - self.allocation = MaterialNonBindlessBindGroupAllocation::Allocated; - MaterialBindGroupSlot(0) + /// If the resource isn't in this binding array, this method returns `None`. + fn find(&self, binding_resource_id: BindingResourceId) -> Option { + self.resource_to_slot.get(&binding_resource_id).copied() } - /// Assigns an unprepared bind group to the group and slot specified in the - /// [`MaterialBindingId`]. - /// - /// For non-bindless bind groups, we go ahead and create the bind group - /// immediately. - fn init( - &mut self, - render_device: &RenderDevice, - bind_group_layout: &BindGroupLayout, - _: MaterialBindGroupSlot, - unprepared_bind_group: UnpreparedBindGroup, - ) { - let entries = unprepared_bind_group - .bindings - .iter() - .map(|(index, binding)| BindGroupEntry { - binding: *index, - resource: binding.get_binding(), - }) - .collect::>(); - - self.allocation = MaterialNonBindlessBindGroupAllocation::Initialized( - render_device.create_bind_group(M::label(), bind_group_layout, &entries), - unprepared_bind_group.data, - ); - } - - /// Fills the slot directly with a custom bind group. - /// - /// This is only a meaningful operation for non-bindless bind groups. It's - /// rarely used, but see the `texture_binding_array` example for an example - /// demonstrating how this feature might see use in practice. - fn init_custom(&mut self, bind_group: BindGroup, extra_data: M::Data) { - self.allocation = - MaterialNonBindlessBindGroupAllocation::Initialized(bind_group, extra_data); - } + /// Inserts a bindless resource into a binding array and returns the index + /// of the slot it was inserted into. + fn insert(&mut self, binding_resource_id: BindingResourceId, resource: R) -> u32 { + let slot = self.free_slots.pop().unwrap_or(self.len); + self.resource_to_slot.insert(binding_resource_id, slot); - /// Deletes the stored bind group. - fn free(&mut self, _: MaterialBindGroupSlot) { - self.allocation = MaterialNonBindlessBindGroupAllocation::Unallocated; - } + if self.bindings.len() < slot as usize + 1 { + self.bindings.resize_with(slot as usize + 1, || None); + } + self.bindings[slot as usize] = Some(MaterialBindlessBinding::new(resource)); - /// Returns true if the slot is full or false if it's free. - fn is_full(&self) -> bool { - !matches!( - self.allocation, - MaterialNonBindlessBindGroupAllocation::Unallocated - ) + self.len += 1; + slot } - /// Returns the actual bind group, or `None` if it hasn't been created yet. - fn get_bind_group(&self) -> Option<&BindGroup> { - match self.allocation { - MaterialNonBindlessBindGroupAllocation::Unallocated - | MaterialNonBindlessBindGroupAllocation::Allocated => None, - MaterialNonBindlessBindGroupAllocation::Initialized(ref bind_group, _) => { - Some(bind_group) - } + /// Removes a reference to an object from the slot. + /// + /// If the reference count dropped to 0 and the object was freed, this + /// method returns true. If the object was still referenced after removing + /// it, returns false. + fn remove(&mut self, slot: u32) -> bool { + let maybe_binding = &mut self.bindings[slot as usize]; + let binding = maybe_binding + .as_mut() + .expect("Attempted to free an already-freed binding"); + + binding.ref_count -= 1; + if binding.ref_count != 0 { + return false; } - } - /// Returns the associated extra data for the material. - fn get_extra_data(&self, _: MaterialBindGroupSlot) -> &M::Data { - match self.allocation { - MaterialNonBindlessBindGroupAllocation::Initialized(_, ref extra_data) => extra_data, - MaterialNonBindlessBindGroupAllocation::Unallocated - | MaterialNonBindlessBindGroupAllocation::Allocated => { - panic!("Bind group not initialized") - } - } + let binding_resource_id = binding.resource.binding_resource_id(self.resource_type); + self.resource_to_slot.remove(&binding_resource_id); + + *maybe_binding = None; + self.free_slots.push(slot); + self.len -= 1; + true } } -impl FromWorld for MaterialBindGroupAllocator +impl MaterialBindlessBinding where - M: Material, + R: GetBindingResourceId, { - fn from_world(world: &mut World) -> Self { - // Create a new bind group allocator. - let render_device = world.resource::(); - let bind_group_layout_entries = M::bind_group_layout_entries(render_device, false); - let bind_group_layout = - render_device.create_bind_group_layout(M::label(), &bind_group_layout_entries); - let fallback_buffers = - MaterialFallbackBuffers::new(render_device, &bind_group_layout_entries); - MaterialBindGroupAllocator { - bind_groups: vec![], - free_bind_groups: vec![], - bind_group_layout, - fallback_buffers, - bindless_enabled: material_uses_bindless_resources::(render_device), - phantom: PhantomData, + /// Creates a new [`MaterialBindlessBinding`] for a freshly-added resource. + /// + /// The reference count is initialized to 1. + fn new(resource: R) -> MaterialBindlessBinding { + MaterialBindlessBinding { + resource, + ref_count: 1, } } } @@ -796,54 +1439,284 @@ pub fn material_uses_bindless_resources(render_device: &RenderDevice) -> bool where M: Material, { - M::bindless_slot_count().is_some() && M::bindless_supported(render_device) + M::bindless_slot_count().is_some_and(|bindless_slot_count| { + M::bindless_supported(render_device) && bindless_slot_count.resolve() > 1 + }) +} + +impl MaterialBindlessSlab +where + M: Material, +{ + /// Creates a new [`MaterialBindlessSlab`] for a material with the given + /// bindless descriptor. + /// + /// We use this when no existing slab could hold a material to be allocated. + fn new(bindless_descriptor: &BindlessDescriptor) -> MaterialBindlessSlab { + let mut buffers = HashMap::default(); + let mut samplers = HashMap::default(); + let mut textures = HashMap::default(); + + for (bindless_index, bindless_resource_type) in + bindless_descriptor.resources.iter().enumerate() + { + let bindless_index = BindlessIndex(bindless_index as u32); + match *bindless_resource_type { + BindlessResourceType::None => {} + BindlessResourceType::Buffer => { + let binding_number = bindless_descriptor + .buffers + .iter() + .find(|bindless_buffer_descriptor| { + bindless_buffer_descriptor.bindless_index == bindless_index + }) + .expect( + "Bindless buffer descriptor matching that bindless index should be \ + present", + ) + .binding_number; + buffers.insert( + bindless_index, + MaterialBindlessBindingArray::new(binding_number, *bindless_resource_type), + ); + } + BindlessResourceType::SamplerFiltering + | BindlessResourceType::SamplerNonFiltering + | BindlessResourceType::SamplerComparison => { + samplers.insert( + *bindless_resource_type, + MaterialBindlessBindingArray::new( + *bindless_resource_type.binding_number().unwrap(), + *bindless_resource_type, + ), + ); + } + BindlessResourceType::Texture1d + | BindlessResourceType::Texture2d + | BindlessResourceType::Texture2dArray + | BindlessResourceType::Texture3d + | BindlessResourceType::TextureCube + | BindlessResourceType::TextureCubeArray => { + textures.insert( + *bindless_resource_type, + MaterialBindlessBindingArray::new( + *bindless_resource_type.binding_number().unwrap(), + *bindless_resource_type, + ), + ); + } + } + } + + MaterialBindlessSlab { + bind_group: None, + bindless_index_table: MaterialBindlessIndexTable::new(bindless_descriptor), + samplers, + textures, + buffers, + extra_data: vec![], + free_slots: vec![], + live_allocation_count: 0, + allocated_resource_count: 0, + } + } } impl FromWorld for FallbackBindlessResources { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); FallbackBindlessResources { - fallback_sampler: render_device.create_sampler(&SamplerDescriptor { - label: Some("fallback sampler"), + filtering_sampler: render_device.create_sampler(&SamplerDescriptor { + label: Some("fallback filtering sampler"), + ..default() + }), + non_filtering_sampler: render_device.create_sampler(&SamplerDescriptor { + label: Some("fallback non-filtering sampler"), + mag_filter: FilterMode::Nearest, + min_filter: FilterMode::Nearest, + mipmap_filter: FilterMode::Nearest, + ..default() + }), + comparison_sampler: render_device.create_sampler(&SamplerDescriptor { + label: Some("fallback comparison sampler"), + compare: Some(CompareFunction::Always), ..default() }), } } } -impl MaterialFallbackBuffers { - /// Creates a new set of fallback buffers containing dummy allocations. +impl MaterialBindGroupNonBindlessAllocator +where + M: Material, +{ + /// Creates a new [`MaterialBindGroupNonBindlessAllocator`] managing the + /// bind groups for a single non-bindless material. + fn new() -> MaterialBindGroupNonBindlessAllocator { + MaterialBindGroupNonBindlessAllocator { + bind_groups: vec![], + to_prepare: HashSet::default(), + free_indices: vec![], + phantom: PhantomData, + } + } + + /// Inserts a bind group, either unprepared or prepared, into this allocator + /// and returns a [`MaterialBindingId`]. /// - /// We populate unused bind group slots with these. - fn new( - render_device: &RenderDevice, - bind_group_layout_entries: &[BindGroupLayoutEntry], - ) -> MaterialFallbackBuffers { - let mut fallback_buffers = HashMap::default(); - for bind_group_layout_entry in bind_group_layout_entries { - // Create a dummy buffer of the appropriate size. - let BindingType::Buffer { - min_binding_size, .. - } = bind_group_layout_entry.ty + /// The returned [`MaterialBindingId`] can later be used to fetch the bind + /// group. + fn allocate( + &mut self, + bind_group: MaterialNonBindlessAllocatedBindGroup, + ) -> MaterialBindingId { + let group_id = self + .free_indices + .pop() + .unwrap_or(MaterialBindGroupIndex(self.bind_groups.len() as u32)); + if self.bind_groups.len() < *group_id as usize + 1 { + self.bind_groups + .resize_with(*group_id as usize + 1, || None); + } + + if matches!( + bind_group, + MaterialNonBindlessAllocatedBindGroup::Unprepared { .. } + ) { + self.to_prepare.insert(group_id); + } + + self.bind_groups[*group_id as usize] = Some(bind_group); + + MaterialBindingId { + group: group_id, + slot: default(), + } + } + + /// Inserts an unprepared bind group into this allocator and returns a + /// [`MaterialBindingId`]. + fn allocate_unprepared( + &mut self, + unprepared_bind_group: UnpreparedBindGroup, + bind_group_layout: BindGroupLayout, + ) -> MaterialBindingId { + self.allocate(MaterialNonBindlessAllocatedBindGroup::Unprepared { + bind_group: unprepared_bind_group, + layout: bind_group_layout, + }) + } + + /// Inserts an prepared bind group into this allocator and returns a + /// [`MaterialBindingId`]. + fn allocate_prepared( + &mut self, + prepared_bind_group: PreparedBindGroup, + ) -> MaterialBindingId { + self.allocate(MaterialNonBindlessAllocatedBindGroup::Prepared( + prepared_bind_group, + )) + } + + /// Deallocates the bind group with the given binding ID. + fn free(&mut self, binding_id: MaterialBindingId) { + debug_assert_eq!(binding_id.slot, MaterialBindGroupSlot(0)); + debug_assert!(self.bind_groups[*binding_id.group as usize].is_some()); + self.bind_groups[*binding_id.group as usize] = None; + self.to_prepare.remove(&binding_id.group); + self.free_indices.push(binding_id.group); + } + + /// Returns a wrapper around the bind group with the given index. + fn get(&self, group: MaterialBindGroupIndex) -> Option> { + self.bind_groups[group.0 as usize] + .as_ref() + .map(|bind_group| match bind_group { + MaterialNonBindlessAllocatedBindGroup::Prepared(prepared_bind_group) => { + MaterialNonBindlessSlab::Prepared(prepared_bind_group) + } + MaterialNonBindlessAllocatedBindGroup::Unprepared { bind_group, .. } => { + MaterialNonBindlessSlab::Unprepared(bind_group) + } + }) + } + + /// Prepares any as-yet unprepared bind groups that this allocator is + /// managing. + /// + /// Unprepared bind groups can be added to this allocator with + /// [`Self::allocate_unprepared`]. Such bind groups will defer being + /// prepared until the next time this method is called. + fn prepare_bind_groups(&mut self, render_device: &RenderDevice) { + for bind_group_index in mem::take(&mut self.to_prepare) { + let Some(MaterialNonBindlessAllocatedBindGroup::Unprepared { + bind_group: unprepared_bind_group, + layout: bind_group_layout, + }) = mem::take(&mut self.bind_groups[*bind_group_index as usize]) else { - continue; - }; - let mut size: u64 = match min_binding_size { - None => 0, - Some(min_binding_size) => min_binding_size.into(), + panic!("Allocation didn't exist or was already prepared"); }; - size = size.max(MIN_BUFFER_SIZE); - - fallback_buffers.insert( - bind_group_layout_entry.binding, - render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("fallback buffer"), - contents: &vec![0; size as usize], - usage: BufferUsages::UNIFORM | BufferUsages::STORAGE, + + let entries: Vec<_> = unprepared_bind_group + .bindings + .iter() + .map(|(index, binding)| BindGroupEntry { + binding: *index, + resource: binding.get_binding(), + }) + .collect(); + + let bind_group = + render_device.create_bind_group(M::label(), &bind_group_layout, &entries); + + self.bind_groups[*bind_group_index as usize] = Some( + MaterialNonBindlessAllocatedBindGroup::Prepared(PreparedBindGroup { + bindings: unprepared_bind_group.bindings, + bind_group, + data: unprepared_bind_group.data, }), ); } + } +} + +impl<'a, M> MaterialSlab<'a, M> +where + M: Material, +{ + /// Returns the extra data associated with this material. + /// + /// When deriving `AsBindGroup`, this data is given by the + /// `#[bind_group_data(DataType)]` attribute on the material structure. + pub fn get_extra_data(&self, slot: MaterialBindGroupSlot) -> &M::Data { + match self.0 { + MaterialSlabImpl::Bindless(material_bindless_slab) => { + material_bindless_slab.get_extra_data(slot) + } + MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared( + prepared_bind_group, + )) => &prepared_bind_group.data, + MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared( + unprepared_bind_group, + )) => &unprepared_bind_group.data, + } + } - MaterialFallbackBuffers(fallback_buffers) + /// Returns the [`BindGroup`] corresponding to this slab, if it's been + /// prepared. + /// + /// You can prepare bind groups by calling + /// [`MaterialBindGroupAllocator::prepare_bind_groups`]. If the bind group + /// isn't ready, this method returns `None`. + pub fn bind_group(&self) -> Option<&'a BindGroup> { + match self.0 { + MaterialSlabImpl::Bindless(material_bindless_slab) => { + material_bindless_slab.bind_group() + } + MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Prepared( + prepared_bind_group, + )) => Some(&prepared_bind_group.bind_group), + MaterialSlabImpl::NonBindless(MaterialNonBindlessSlab::Unprepared(_)) => None, + } } } diff --git a/crates/bevy_pbr/src/mesh_material.rs b/crates/bevy_pbr/src/mesh_material.rs index 84eaf7cffa79a..c3f81943ecbcf 100644 --- a/crates/bevy_pbr/src/mesh_material.rs +++ b/crates/bevy_pbr/src/mesh_material.rs @@ -1,5 +1,5 @@ use crate::Material; -use bevy_asset::{AssetId, Handle}; +use bevy_asset::{AsAssetId, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -57,3 +57,11 @@ impl From<&MeshMaterial3d> for AssetId { material.id() } } + +impl AsAssetId for MeshMaterial3d { + type Asset = M; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} diff --git a/crates/bevy_pbr/src/meshlet/clear_visibility_buffer.wgsl b/crates/bevy_pbr/src/meshlet/clear_visibility_buffer.wgsl new file mode 100644 index 0000000000000..5956921ca1609 --- /dev/null +++ b/crates/bevy_pbr/src/meshlet/clear_visibility_buffer.wgsl @@ -0,0 +1,18 @@ +#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT +@group(0) @binding(0) var meshlet_visibility_buffer: texture_storage_2d; +#else +@group(0) @binding(0) var meshlet_visibility_buffer: texture_storage_2d; +#endif +var view_size: vec2; + +@compute +@workgroup_size(16, 16, 1) +fn clear_visibility_buffer(@builtin(global_invocation_id) global_id: vec3) { + if any(global_id.xy >= view_size) { return; } + +#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT + textureStore(meshlet_visibility_buffer, global_id.xy, vec4(0lu)); +#else + textureStore(meshlet_visibility_buffer, global_id.xy, vec4(0u)); +#endif +} diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index 0e71d5a9d9dcf..2351c7756d9c2 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -228,7 +228,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( else { continue; }; - let Some(bind_group) = material_bind_group.get_bind_group() else { + let Some(bind_group) = material_bind_group.bind_group() else { continue; }; @@ -399,7 +399,7 @@ pub fn prepare_material_meshlet_meshes_prepass( else { continue; }; - let Some(bind_group) = material_bind_group.get_bind_group() else { + let Some(bind_group) = material_bind_group.bind_group() else { continue; }; diff --git a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl index 7af63d0e0fe83..e179e78b7ae5e 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_bindings.wgsl @@ -100,9 +100,9 @@ fn cluster_is_second_pass_candidate(cluster_id: u32) -> bool { @group(0) @binding(6) var meshlet_raster_clusters: array; // Single object shared between all workgroups @group(0) @binding(7) var meshlet_software_raster_cluster_count: u32; #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT -@group(0) @binding(8) var meshlet_visibility_buffer: array>; // Per pixel +@group(0) @binding(8) var meshlet_visibility_buffer: texture_storage_2d; #else -@group(0) @binding(8) var meshlet_visibility_buffer: array>; // Per pixel +@group(0) @binding(8) var meshlet_visibility_buffer: texture_storage_2d; #endif @group(0) @binding(9) var view: View; @@ -149,7 +149,7 @@ fn get_meshlet_vertex_position(meshlet: ptr, vertex_id: u32) #endif #ifdef MESHLET_MESH_MATERIAL_PASS -@group(1) @binding(0) var meshlet_visibility_buffer: array; // Per pixel +@group(1) @binding(0) var meshlet_visibility_buffer: texture_storage_2d; @group(1) @binding(1) var meshlet_cluster_meshlet_ids: array; // Per cluster @group(1) @binding(2) var meshlets: array; // Per meshlet @group(1) @binding(3) var meshlet_indices: array; // Many per meshlet diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 862db413f88f3..4057f29e39a3c 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -56,10 +56,9 @@ use self::{ }, visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode, }; -use crate::graph::NodePbr; -use crate::PreviousGlobalTransform; +use crate::{graph::NodePbr, PreviousGlobalTransform}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, AssetApp, AssetId, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass}, @@ -86,9 +85,10 @@ use bevy_transform::components::Transform; use derive_more::From; use tracing::error; -const MESHLET_BINDINGS_SHADER_HANDLE: Handle = Handle::weak_from_u128(1325134235233421); +const MESHLET_BINDINGS_SHADER_HANDLE: Handle = + weak_handle!("d90ac78c-500f-48aa-b488-cc98eb3f6314"); const MESHLET_MESH_MATERIAL_SHADER_HANDLE: Handle = - Handle::weak_from_u128(3325134235233421); + weak_handle!("db8d9001-6ca7-4d00-968a-d5f5b96b89c3"); /// Provides a plugin for rendering large amounts of high-poly 3d meshes using an efficient GPU-driven method. See also [`MeshletMesh`]. /// @@ -106,9 +106,9 @@ const MESHLET_MESH_MATERIAL_SHADER_HANDLE: Handle = /// * Requires preprocessing meshes. See [`MeshletMesh`] for details. /// * Limitations on the kinds of materials you can use. See [`MeshletMesh`] for details. /// -/// This plugin requires a fairly recent GPU that supports [`WgpuFeatures::SHADER_INT64_ATOMIC_MIN_MAX`]. +/// This plugin requires a fairly recent GPU that supports [`WgpuFeatures::TEXTURE_INT64_ATOMIC`]. /// -/// This plugin currently works only on the Vulkan backend. +/// This plugin currently works only on the Vulkan and Metal backends. /// /// This plugin is not compatible with [`Msaa`]. Any camera rendering a [`MeshletMesh`] must have /// [`Msaa`] set to [`Msaa::Off`]. @@ -133,7 +133,8 @@ pub struct MeshletPlugin { impl MeshletPlugin { /// [`WgpuFeatures`] required for this plugin to function. pub fn required_wgpu_features() -> WgpuFeatures { - WgpuFeatures::SHADER_INT64_ATOMIC_MIN_MAX + WgpuFeatures::TEXTURE_INT64_ATOMIC + | WgpuFeatures::TEXTURE_ATOMIC | WgpuFeatures::SHADER_INT64 | WgpuFeatures::SUBGROUP | WgpuFeatures::DEPTH_CLIP_CONTROL @@ -151,6 +152,12 @@ impl Plugin for MeshletPlugin { std::process::exit(1); } + load_internal_asset!( + app, + MESHLET_CLEAR_VISIBILITY_BUFFER_SHADER_HANDLE, + "clear_visibility_buffer.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, MESHLET_BINDINGS_SHADER_HANDLE, @@ -246,13 +253,11 @@ impl Plugin for MeshletPlugin { Core3d, ( NodeMeshlet::VisibilityBufferRasterPass, - NodePbr::ShadowPass, + NodePbr::EarlyShadowPass, // NodeMeshlet::Prepass, // NodeMeshlet::DeferredPrepass, - Node3d::DeferredPrepass, - Node3d::CopyDeferredLightingId, Node3d::EndPrepasses, // Node3d::StartMainPass, diff --git a/crates/bevy_pbr/src/meshlet/pipelines.rs b/crates/bevy_pbr/src/meshlet/pipelines.rs index e5e8ec44d829d..c25d896b8a385 100644 --- a/crates/bevy_pbr/src/meshlet/pipelines.rs +++ b/crates/bevy_pbr/src/meshlet/pipelines.rs @@ -1,5 +1,5 @@ use super::resource_manager::ResourceManager; -use bevy_asset::Handle; +use bevy_asset::{weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DOWNSAMPLE_DEPTH_SHADER_HANDLE, fullscreen_vertex_shader::fullscreen_shader_vertex_state, @@ -10,21 +10,26 @@ use bevy_ecs::{ }; use bevy_render::render_resource::*; +pub const MESHLET_CLEAR_VISIBILITY_BUFFER_SHADER_HANDLE: Handle = + weak_handle!("a4bf48e4-5605-4d1c-987e-29c7b1ec95dc"); pub const MESHLET_FILL_CLUSTER_BUFFERS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(4325134235233421); -pub const MESHLET_CULLING_SHADER_HANDLE: Handle = Handle::weak_from_u128(5325134235233421); + weak_handle!("80ccea4a-8234-4ee0-af74-77b3cad503cf"); +pub const MESHLET_CULLING_SHADER_HANDLE: Handle = + weak_handle!("d71c5879-97fa-49d1-943e-ed9162fe8adb"); pub const MESHLET_VISIBILITY_BUFFER_SOFTWARE_RASTER_SHADER_HANDLE: Handle = - Handle::weak_from_u128(7325134235233421); + weak_handle!("68cc6826-8321-43d1-93d5-4f61f0456c13"); pub const MESHLET_VISIBILITY_BUFFER_HARDWARE_RASTER_SHADER_HANDLE: Handle = - Handle::weak_from_u128(8325134235233421); + weak_handle!("4b4e3020-748f-4baf-b011-87d9d2a12796"); pub const MESHLET_RESOLVE_RENDER_TARGETS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(9325134235233421); + weak_handle!("c218ce17-cf59-4268-8898-13ecf384f133"); pub const MESHLET_REMAP_1D_TO_2D_DISPATCH_SHADER_HANDLE: Handle = - Handle::weak_from_u128(9425134235233421); + weak_handle!("f5b7edfc-2eac-4407-8f5c-1265d4d795c2"); #[derive(Resource)] pub struct MeshletPipelines { fill_cluster_buffers: CachedComputePipelineId, + clear_visibility_buffer: CachedComputePipelineId, + clear_visibility_buffer_shadow_view: CachedComputePipelineId, cull_first: CachedComputePipelineId, cull_second: CachedComputePipelineId, downsample_depth_first: CachedComputePipelineId, @@ -32,10 +37,10 @@ pub struct MeshletPipelines { downsample_depth_first_shadow_view: CachedComputePipelineId, downsample_depth_second_shadow_view: CachedComputePipelineId, visibility_buffer_software_raster: CachedComputePipelineId, - visibility_buffer_software_raster_depth_only: CachedComputePipelineId, + visibility_buffer_software_raster_shadow_view: CachedComputePipelineId, visibility_buffer_hardware_raster: CachedRenderPipelineId, - visibility_buffer_hardware_raster_depth_only: CachedRenderPipelineId, - visibility_buffer_hardware_raster_depth_only_unclipped: CachedRenderPipelineId, + visibility_buffer_hardware_raster_shadow_view: CachedRenderPipelineId, + visibility_buffer_hardware_raster_shadow_view_unclipped: CachedRenderPipelineId, resolve_depth: CachedRenderPipelineId, resolve_depth_shadow_view: CachedRenderPipelineId, resolve_material_depth: CachedRenderPipelineId, @@ -48,12 +53,27 @@ impl FromWorld for MeshletPipelines { let fill_cluster_buffers_bind_group_layout = resource_manager .fill_cluster_buffers_bind_group_layout .clone(); + let clear_visibility_buffer_bind_group_layout = resource_manager + .clear_visibility_buffer_bind_group_layout + .clone(); + let clear_visibility_buffer_shadow_view_bind_group_layout = resource_manager + .clear_visibility_buffer_shadow_view_bind_group_layout + .clone(); let cull_layout = resource_manager.culling_bind_group_layout.clone(); let downsample_depth_layout = resource_manager.downsample_depth_bind_group_layout.clone(); + let downsample_depth_shadow_view_layout = resource_manager + .downsample_depth_shadow_view_bind_group_layout + .clone(); let visibility_buffer_raster_layout = resource_manager .visibility_buffer_raster_bind_group_layout .clone(); + let visibility_buffer_raster_shadow_view_layout = resource_manager + .visibility_buffer_raster_shadow_view_bind_group_layout + .clone(); let resolve_depth_layout = resource_manager.resolve_depth_bind_group_layout.clone(); + let resolve_depth_shadow_view_layout = resource_manager + .resolve_depth_shadow_view_bind_group_layout + .clone(); let resolve_material_depth_layout = resource_manager .resolve_material_depth_bind_group_layout .clone(); @@ -66,7 +86,7 @@ impl FromWorld for MeshletPipelines { fill_cluster_buffers: pipeline_cache.queue_compute_pipeline( ComputePipelineDescriptor { label: Some("meshlet_fill_cluster_buffers_pipeline".into()), - layout: vec![fill_cluster_buffers_bind_group_layout.clone()], + layout: vec![fill_cluster_buffers_bind_group_layout], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, range: 0..4, @@ -78,6 +98,36 @@ impl FromWorld for MeshletPipelines { }, ), + clear_visibility_buffer: pipeline_cache.queue_compute_pipeline( + ComputePipelineDescriptor { + label: Some("meshlet_clear_visibility_buffer_pipeline".into()), + layout: vec![clear_visibility_buffer_bind_group_layout], + push_constant_ranges: vec![PushConstantRange { + stages: ShaderStages::COMPUTE, + range: 0..8, + }], + shader: MESHLET_CLEAR_VISIBILITY_BUFFER_SHADER_HANDLE, + shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into()], + entry_point: "clear_visibility_buffer".into(), + zero_initialize_workgroup_memory: false, + }, + ), + + clear_visibility_buffer_shadow_view: pipeline_cache.queue_compute_pipeline( + ComputePipelineDescriptor { + label: Some("meshlet_clear_visibility_buffer_shadow_view_pipeline".into()), + layout: vec![clear_visibility_buffer_shadow_view_bind_group_layout], + push_constant_ranges: vec![PushConstantRange { + stages: ShaderStages::COMPUTE, + range: 0..8, + }], + shader: MESHLET_CLEAR_VISIBILITY_BUFFER_SHADER_HANDLE, + shader_defs: vec![], + entry_point: "clear_visibility_buffer".into(), + zero_initialize_workgroup_memory: false, + }, + ), + cull_first: pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { label: Some("meshlet_culling_first_pipeline".into()), layout: vec![cull_layout.clone()], @@ -116,7 +166,7 @@ impl FromWorld for MeshletPipelines { layout: vec![downsample_depth_layout.clone()], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, - range: 0..8, + range: 0..4, }], shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader_defs: vec![ @@ -134,7 +184,7 @@ impl FromWorld for MeshletPipelines { layout: vec![downsample_depth_layout.clone()], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, - range: 0..8, + range: 0..4, }], shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader_defs: vec![ @@ -149,10 +199,10 @@ impl FromWorld for MeshletPipelines { downsample_depth_first_shadow_view: pipeline_cache.queue_compute_pipeline( ComputePipelineDescriptor { label: Some("meshlet_downsample_depth_first_pipeline".into()), - layout: vec![downsample_depth_layout.clone()], + layout: vec![downsample_depth_shadow_view_layout.clone()], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, - range: 0..8, + range: 0..4, }], shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader_defs: vec!["MESHLET".into()], @@ -164,10 +214,10 @@ impl FromWorld for MeshletPipelines { downsample_depth_second_shadow_view: pipeline_cache.queue_compute_pipeline( ComputePipelineDescriptor { label: Some("meshlet_downsample_depth_second_pipeline".into()), - layout: vec![downsample_depth_layout], + layout: vec![downsample_depth_shadow_view_layout], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::COMPUTE, - range: 0..8, + range: 0..4, }], shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader_defs: vec!["MESHLET".into()], @@ -197,12 +247,12 @@ impl FromWorld for MeshletPipelines { }, ), - visibility_buffer_software_raster_depth_only: pipeline_cache.queue_compute_pipeline( + visibility_buffer_software_raster_shadow_view: pipeline_cache.queue_compute_pipeline( ComputePipelineDescriptor { label: Some( - "meshlet_visibility_buffer_software_raster_depth_only_pipeline".into(), + "meshlet_visibility_buffer_software_raster_shadow_view_pipeline".into(), ), - layout: vec![visibility_buffer_raster_layout.clone()], + layout: vec![visibility_buffer_raster_shadow_view_layout.clone()], push_constant_ranges: vec![], shader: MESHLET_VISIBILITY_BUFFER_SOFTWARE_RASTER_SHADER_HANDLE, shader_defs: vec![ @@ -264,12 +314,12 @@ impl FromWorld for MeshletPipelines { }, ), - visibility_buffer_hardware_raster_depth_only: pipeline_cache.queue_render_pipeline( + visibility_buffer_hardware_raster_shadow_view: pipeline_cache.queue_render_pipeline( RenderPipelineDescriptor { label: Some( - "meshlet_visibility_buffer_hardware_raster_depth_only_pipeline".into(), + "meshlet_visibility_buffer_hardware_raster_shadow_view_pipeline".into(), ), - layout: vec![visibility_buffer_raster_layout.clone()], + layout: vec![visibility_buffer_raster_shadow_view_layout.clone()], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::VERTEX, range: 0..4, @@ -305,13 +355,13 @@ impl FromWorld for MeshletPipelines { }, ), - visibility_buffer_hardware_raster_depth_only_unclipped: pipeline_cache + visibility_buffer_hardware_raster_shadow_view_unclipped: pipeline_cache .queue_render_pipeline(RenderPipelineDescriptor { label: Some( - "meshlet_visibility_buffer_hardware_raster_depth_only_unclipped_pipeline" + "meshlet_visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline" .into(), ), - layout: vec![visibility_buffer_raster_layout], + layout: vec![visibility_buffer_raster_shadow_view_layout], push_constant_ranges: vec![PushConstantRange { stages: ShaderStages::VERTEX, range: 0..4, @@ -348,17 +398,14 @@ impl FromWorld for MeshletPipelines { resolve_depth: pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { label: Some("meshlet_resolve_depth_pipeline".into()), - layout: vec![resolve_depth_layout.clone()], - push_constant_ranges: vec![PushConstantRange { - stages: ShaderStages::FRAGMENT, - range: 0..4, - }], + layout: vec![resolve_depth_layout], + push_constant_ranges: vec![], vertex: fullscreen_shader_vertex_state(), primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, - depth_compare: CompareFunction::GreaterEqual, + depth_compare: CompareFunction::Always, stencil: StencilState::default(), bias: DepthBiasState::default(), }), @@ -375,17 +422,14 @@ impl FromWorld for MeshletPipelines { resolve_depth_shadow_view: pipeline_cache.queue_render_pipeline( RenderPipelineDescriptor { label: Some("meshlet_resolve_depth_pipeline".into()), - layout: vec![resolve_depth_layout], - push_constant_ranges: vec![PushConstantRange { - stages: ShaderStages::FRAGMENT, - range: 0..4, - }], + layout: vec![resolve_depth_shadow_view_layout], + push_constant_ranges: vec![], vertex: fullscreen_shader_vertex_state(), primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, - depth_compare: CompareFunction::GreaterEqual, + depth_compare: CompareFunction::Always, stencil: StencilState::default(), bias: DepthBiasState::default(), }), @@ -404,10 +448,7 @@ impl FromWorld for MeshletPipelines { RenderPipelineDescriptor { label: Some("meshlet_resolve_material_depth_pipeline".into()), layout: vec![resolve_material_depth_layout], - push_constant_ranges: vec![PushConstantRange { - stages: ShaderStages::FRAGMENT, - range: 0..4, - }], + push_constant_ranges: vec![], vertex: fullscreen_shader_vertex_state(), primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { @@ -459,6 +500,8 @@ impl MeshletPipelines { &ComputePipeline, &ComputePipeline, &ComputePipeline, + &ComputePipeline, + &ComputePipeline, &RenderPipeline, &RenderPipeline, &RenderPipeline, @@ -471,6 +514,8 @@ impl MeshletPipelines { let pipeline = world.get_resource::()?; Some(( pipeline_cache.get_compute_pipeline(pipeline.fill_cluster_buffers)?, + pipeline_cache.get_compute_pipeline(pipeline.clear_visibility_buffer)?, + pipeline_cache.get_compute_pipeline(pipeline.clear_visibility_buffer_shadow_view)?, pipeline_cache.get_compute_pipeline(pipeline.cull_first)?, pipeline_cache.get_compute_pipeline(pipeline.cull_second)?, pipeline_cache.get_compute_pipeline(pipeline.downsample_depth_first)?, @@ -479,12 +524,12 @@ impl MeshletPipelines { pipeline_cache.get_compute_pipeline(pipeline.downsample_depth_second_shadow_view)?, pipeline_cache.get_compute_pipeline(pipeline.visibility_buffer_software_raster)?, pipeline_cache - .get_compute_pipeline(pipeline.visibility_buffer_software_raster_depth_only)?, + .get_compute_pipeline(pipeline.visibility_buffer_software_raster_shadow_view)?, pipeline_cache.get_render_pipeline(pipeline.visibility_buffer_hardware_raster)?, pipeline_cache - .get_render_pipeline(pipeline.visibility_buffer_hardware_raster_depth_only)?, + .get_render_pipeline(pipeline.visibility_buffer_hardware_raster_shadow_view)?, pipeline_cache.get_render_pipeline( - pipeline.visibility_buffer_hardware_raster_depth_only_unclipped, + pipeline.visibility_buffer_hardware_raster_shadow_view_unclipped, )?, pipeline_cache.get_render_pipeline(pipeline.resolve_depth)?, pipeline_cache.get_render_pipeline(pipeline.resolve_depth_shadow_view)?, diff --git a/crates/bevy_pbr/src/meshlet/resolve_render_targets.wgsl b/crates/bevy_pbr/src/meshlet/resolve_render_targets.wgsl index 3c0cfcf9436e2..eaa4eed6c4560 100644 --- a/crates/bevy_pbr/src/meshlet/resolve_render_targets.wgsl +++ b/crates/bevy_pbr/src/meshlet/resolve_render_targets.wgsl @@ -1,35 +1,36 @@ #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT -@group(0) @binding(0) var meshlet_visibility_buffer: array; // Per pixel +@group(0) @binding(0) var meshlet_visibility_buffer: texture_storage_2d; #else -@group(0) @binding(0) var meshlet_visibility_buffer: array; // Per pixel +@group(0) @binding(0) var meshlet_visibility_buffer: texture_storage_2d; #endif @group(0) @binding(1) var meshlet_cluster_instance_ids: array; // Per cluster @group(0) @binding(2) var meshlet_instance_material_ids: array; // Per entity instance -var view_width: u32; /// This pass writes out the depth texture. @fragment fn resolve_depth(in: FullscreenVertexOutput) -> @builtin(frag_depth) f32 { - let frag_coord_1d = u32(in.position.y) * view_width + u32(in.position.x); - let visibility = meshlet_visibility_buffer[frag_coord_1d]; + let visibility = textureLoad(meshlet_visibility_buffer, vec2(in.position.xy)).r; #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT - return bitcast(u32(visibility >> 32u)); + let depth = u32(visibility >> 32u); #else - return bitcast(visibility); + let depth = visibility; #endif + + if depth == 0u { discard; } + + return bitcast(depth); } /// This pass writes out the material depth texture. #ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT @fragment fn resolve_material_depth(in: FullscreenVertexOutput) -> @builtin(frag_depth) f32 { - let frag_coord_1d = u32(in.position.y) * view_width + u32(in.position.x); - let visibility = meshlet_visibility_buffer[frag_coord_1d]; + let visibility = textureLoad(meshlet_visibility_buffer, vec2(in.position.xy)).r; let depth = visibility >> 32u; - if depth == 0lu { return 0.0; } + if depth == 0lu { discard; } let cluster_id = u32(visibility) >> 7u; let instance_id = meshlet_cluster_instance_ids[cluster_id]; diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index b2d6cff11ddb1..1d3828b95e184 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -50,10 +50,15 @@ pub struct ResourceManager { // Bind group layouts pub fill_cluster_buffers_bind_group_layout: BindGroupLayout, + pub clear_visibility_buffer_bind_group_layout: BindGroupLayout, + pub clear_visibility_buffer_shadow_view_bind_group_layout: BindGroupLayout, pub culling_bind_group_layout: BindGroupLayout, pub visibility_buffer_raster_bind_group_layout: BindGroupLayout, + pub visibility_buffer_raster_shadow_view_bind_group_layout: BindGroupLayout, pub downsample_depth_bind_group_layout: BindGroupLayout, + pub downsample_depth_shadow_view_bind_group_layout: BindGroupLayout, pub resolve_depth_bind_group_layout: BindGroupLayout, + pub resolve_depth_shadow_view_bind_group_layout: BindGroupLayout, pub resolve_material_depth_bind_group_layout: BindGroupLayout, pub material_shade_bind_group_layout: BindGroupLayout, pub remap_1d_to_2d_dispatch_bind_group_layout: Option, @@ -108,6 +113,21 @@ impl ResourceManager { ), ), ), + clear_visibility_buffer_bind_group_layout: render_device.create_bind_group_layout( + "meshlet_clear_visibility_buffer_bind_group_layout", + &BindGroupLayoutEntries::single( + ShaderStages::COMPUTE, + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::WriteOnly), + ), + ), + clear_visibility_buffer_shadow_view_bind_group_layout: render_device + .create_bind_group_layout( + "meshlet_clear_visibility_buffer_shadow_view_bind_group_layout", + &BindGroupLayoutEntries::single( + ShaderStages::COMPUTE, + texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly), + ), + ), culling_bind_group_layout: render_device.create_bind_group_layout( "meshlet_culling_bind_group_layout", &BindGroupLayoutEntries::sequential( @@ -136,7 +156,34 @@ impl ResourceManager { texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly) }; ( - storage_buffer_read_only_sized(false, None), + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::ReadOnly), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + texture_storage_2d( + TextureFormat::R32Float, + StorageTextureAccess::ReadWrite, + ), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + write_only_r32float(), + sampler(SamplerBindingType::NonFiltering), + ) + }), + ), + downsample_depth_shadow_view_bind_group_layout: render_device.create_bind_group_layout( + "meshlet_downsample_depth_shadow_view_bind_group_layout", + &BindGroupLayoutEntries::sequential(ShaderStages::COMPUTE, { + let write_only_r32float = || { + texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly) + }; + ( + texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::ReadOnly), write_only_r32float(), write_only_r32float(), write_only_r32float(), @@ -169,16 +216,45 @@ impl ResourceManager { storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), - storage_buffer_sized(false, None), + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::Atomic), uniform_buffer::(true), ), ), ), + visibility_buffer_raster_shadow_view_bind_group_layout: render_device + .create_bind_group_layout( + "meshlet_visibility_buffer_raster_shadow_view_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::all(), + ( + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + storage_buffer_read_only_sized(false, None), + texture_storage_2d( + TextureFormat::R32Uint, + StorageTextureAccess::Atomic, + ), + uniform_buffer::(true), + ), + ), + ), resolve_depth_bind_group_layout: render_device.create_bind_group_layout( "meshlet_resolve_depth_bind_group_layout", &BindGroupLayoutEntries::single( ShaderStages::FRAGMENT, - storage_buffer_read_only_sized(false, None), + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::ReadOnly), + ), + ), + resolve_depth_shadow_view_bind_group_layout: render_device.create_bind_group_layout( + "meshlet_resolve_depth_shadow_view_bind_group_layout", + &BindGroupLayoutEntries::single( + ShaderStages::FRAGMENT, + texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::ReadOnly), ), ), resolve_material_depth_bind_group_layout: render_device.create_bind_group_layout( @@ -186,7 +262,7 @@ impl ResourceManager { &BindGroupLayoutEntries::sequential( ShaderStages::FRAGMENT, ( - storage_buffer_read_only_sized(false, None), + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::ReadOnly), storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), ), @@ -197,7 +273,7 @@ impl ResourceManager { &BindGroupLayoutEntries::sequential( ShaderStages::FRAGMENT, ( - storage_buffer_read_only_sized(false, None), + texture_storage_2d(TextureFormat::R64Uint, StorageTextureAccess::ReadOnly), storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), storage_buffer_read_only_sized(false, None), @@ -234,7 +310,7 @@ pub struct MeshletViewResources { pub second_pass_candidates_buffer: Buffer, instance_visibility: Buffer, pub dummy_render_target: CachedTexture, - pub visibility_buffer: Buffer, + pub visibility_buffer: CachedTexture, pub visibility_buffer_software_raster_indirect_args_first: Buffer, pub visibility_buffer_software_raster_indirect_args_second: Buffer, pub visibility_buffer_hardware_raster_indirect_args_first: Buffer, @@ -244,12 +320,14 @@ pub struct MeshletViewResources { pub material_depth: Option, pub view_size: UVec2, pub raster_cluster_rightmost_slot: u32, + not_shadow_view: bool, } #[derive(Component)] pub struct MeshletViewBindGroups { pub first_node: Arc, pub fill_cluster_buffers: BindGroup, + pub clear_visibility_buffer: BindGroup, pub culling_first: BindGroup, pub culling_second: BindGroup, pub downsample_depth: BindGroup, @@ -419,18 +497,27 @@ pub fn prepare_meshlet_per_frame_resources( }, ); - let type_size = if not_shadow_view { - size_of::() - } else { - size_of::() - } as u64; - // TODO: Cache - let visibility_buffer = render_device.create_buffer(&BufferDescriptor { - label: Some("meshlet_visibility_buffer"), - size: type_size * (view.viewport.z * view.viewport.w) as u64, - usage: BufferUsages::STORAGE, - mapped_at_creation: false, - }); + let visibility_buffer = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("meshlet_visibility_buffer"), + size: Extent3d { + width: view.viewport.z, + height: view.viewport.w, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: if not_shadow_view { + TextureFormat::R64Uint + } else { + TextureFormat::R32Uint + }, + usage: TextureUsages::STORAGE_ATOMIC | TextureUsages::STORAGE_BINDING, + view_formats: &[], + }, + ); let visibility_buffer_software_raster_indirect_args_first = render_device .create_buffer_with_data(&BufferInitDescriptor { @@ -520,6 +607,7 @@ pub fn prepare_meshlet_per_frame_resources( .then(|| texture_cache.get(&render_device, material_depth)), view_size: view.viewport.zw(), raster_cluster_rightmost_slot: resource_manager.raster_cluster_rightmost_slot, + not_shadow_view, }); } } @@ -577,6 +665,16 @@ pub fn prepare_meshlet_view_bind_groups( &entries, ); + let clear_visibility_buffer = render_device.create_bind_group( + "meshlet_clear_visibility_buffer_bind_group", + if view_resources.not_shadow_view { + &resource_manager.clear_visibility_buffer_bind_group_layout + } else { + &resource_manager.clear_visibility_buffer_shadow_view_bind_group_layout + }, + &BindGroupEntries::single(&view_resources.visibility_buffer.default_view), + ); + let entries = BindGroupEntries::sequential(( cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlet_bounding_spheres.binding(), @@ -638,8 +736,12 @@ pub fn prepare_meshlet_view_bind_groups( let downsample_depth = view_resources.depth_pyramid.create_bind_group( &render_device, "meshlet_downsample_depth_bind_group", - &resource_manager.downsample_depth_bind_group_layout, - view_resources.visibility_buffer.as_entire_binding(), + if view_resources.not_shadow_view { + &resource_manager.downsample_depth_bind_group_layout + } else { + &resource_manager.downsample_depth_shadow_view_bind_group_layout + }, + &view_resources.visibility_buffer.default_view, &resource_manager.depth_pyramid_sampler, ); @@ -656,24 +758,32 @@ pub fn prepare_meshlet_view_bind_groups( resource_manager .software_raster_cluster_count .as_entire_binding(), - view_resources.visibility_buffer.as_entire_binding(), + &view_resources.visibility_buffer.default_view, view_uniforms.clone(), )); let visibility_buffer_raster = render_device.create_bind_group( "meshlet_visibility_raster_buffer_bind_group", - &resource_manager.visibility_buffer_raster_bind_group_layout, + if view_resources.not_shadow_view { + &resource_manager.visibility_buffer_raster_bind_group_layout + } else { + &resource_manager.visibility_buffer_raster_shadow_view_bind_group_layout + }, &entries, ); let resolve_depth = render_device.create_bind_group( "meshlet_resolve_depth_bind_group", - &resource_manager.resolve_depth_bind_group_layout, - &BindGroupEntries::single(view_resources.visibility_buffer.as_entire_binding()), + if view_resources.not_shadow_view { + &resource_manager.resolve_depth_bind_group_layout + } else { + &resource_manager.resolve_depth_shadow_view_bind_group_layout + }, + &BindGroupEntries::single(&view_resources.visibility_buffer.default_view), ); let resolve_material_depth = view_resources.material_depth.as_ref().map(|_| { let entries = BindGroupEntries::sequential(( - view_resources.visibility_buffer.as_entire_binding(), + &view_resources.visibility_buffer.default_view, cluster_instance_ids.as_entire_binding(), instance_manager.instance_material_ids.binding().unwrap(), )); @@ -686,7 +796,7 @@ pub fn prepare_meshlet_view_bind_groups( let material_shade = view_resources.material_depth.as_ref().map(|_| { let entries = BindGroupEntries::sequential(( - view_resources.visibility_buffer.as_entire_binding(), + &view_resources.visibility_buffer.default_view, cluster_meshlet_ids.as_entire_binding(), meshlet_mesh_manager.meshlets.binding(), meshlet_mesh_manager.indices.binding(), @@ -738,6 +848,7 @@ pub fn prepare_meshlet_view_bind_groups( commands.entity(view_entity).insert(MeshletViewBindGroups { first_node: Arc::clone(&first_node), fill_cluster_buffers, + clear_visibility_buffer, culling_first, culling_second, downsample_depth, diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl index fb2e09005175f..3525d38e6da95 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_hardware_raster.wgsl @@ -54,16 +54,13 @@ fn vertex(@builtin(instance_index) instance_index: u32, @builtin(vertex_index) v @fragment fn fragment(vertex_output: VertexOutput) { - let frag_coord_1d = u32(vertex_output.position.y) * u32(view.viewport.z) + u32(vertex_output.position.x); - -#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT let depth = bitcast(vertex_output.position.z); +#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT let visibility = (u64(depth) << 32u) | u64(vertex_output.packed_ids); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility); #else - let depth = bitcast(vertex_output.position.z); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); + let visibility = depth; #endif + textureAtomicMax(meshlet_visibility_buffer, vec2(vertex_output.position.xy), visibility); } fn dummy_vertex() -> VertexOutput { diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index c4376d622ace6..20054d2d2f53a 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ query::QueryState, world::{FromWorld, World}, }; -use bevy_math::ops; +use bevy_math::{ops, UVec2}; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext}, @@ -77,6 +77,8 @@ impl Node for MeshletVisibilityBufferRasterPassNode { let Some(( fill_cluster_buffers_pipeline, + clear_visibility_buffer_pipeline, + clear_visibility_buffer_shadow_view_pipeline, culling_first_pipeline, culling_second_pipeline, downsample_depth_first_pipeline, @@ -84,10 +86,10 @@ impl Node for MeshletVisibilityBufferRasterPassNode { downsample_depth_first_shadow_view_pipeline, downsample_depth_second_shadow_view_pipeline, visibility_buffer_software_raster_pipeline, - visibility_buffer_software_raster_depth_only_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, visibility_buffer_hardware_raster_pipeline, - visibility_buffer_hardware_raster_depth_only_pipeline, - visibility_buffer_hardware_raster_depth_only_unclipped_pipeline, + visibility_buffer_hardware_raster_shadow_view_pipeline, + visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline, resolve_depth_pipeline, resolve_depth_shadow_view_pipeline, resolve_material_depth_pipeline, @@ -107,11 +109,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { render_context .command_encoder() .push_debug_group("meshlet_visibility_buffer_raster"); - render_context.command_encoder().clear_buffer( - &meshlet_view_resources.second_pass_candidates_buffer, - 0, - None, - ); if first_node { fill_cluster_buffers_pass( render_context, @@ -120,6 +117,17 @@ impl Node for MeshletVisibilityBufferRasterPassNode { meshlet_view_resources.scene_instance_count, ); } + clear_visibility_buffer_pass( + render_context, + &meshlet_view_bind_groups.clear_visibility_buffer, + clear_visibility_buffer_pipeline, + meshlet_view_resources.view_size, + ); + render_context.command_encoder().clear_buffer( + &meshlet_view_resources.second_pass_candidates_buffer, + 0, + None, + ); cull_pass( "culling_first", render_context, @@ -150,7 +158,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode { meshlet_view_resources.raster_cluster_rightmost_slot, ); meshlet_view_resources.depth_pyramid.downsample_depth( - "meshlet early downsample depth", + "downsample_depth", render_context, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, @@ -189,7 +197,6 @@ impl Node for MeshletVisibilityBufferRasterPassNode { resolve_depth( render_context, view_depth.get_attachment(StoreOp::Store), - meshlet_view_resources, meshlet_view_bind_groups, resolve_depth_pipeline, camera, @@ -202,7 +209,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode { camera, ); meshlet_view_resources.depth_pyramid.downsample_depth( - "meshlet late downsample depth", + "downsample_depth", render_context, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, @@ -226,15 +233,21 @@ impl Node for MeshletVisibilityBufferRasterPassNode { let shadow_visibility_buffer_hardware_raster_pipeline = if let LightEntity::Directional { .. } = light_type { - visibility_buffer_hardware_raster_depth_only_unclipped_pipeline + visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline } else { - visibility_buffer_hardware_raster_depth_only_pipeline + visibility_buffer_hardware_raster_shadow_view_pipeline }; render_context.command_encoder().push_debug_group(&format!( "meshlet_visibility_buffer_raster: {}", shadow_view.pass_name )); + clear_visibility_buffer_pass( + render_context, + &meshlet_view_bind_groups.clear_visibility_buffer, + clear_visibility_buffer_shadow_view_pipeline, + meshlet_view_resources.view_size, + ); render_context.command_encoder().clear_buffer( &meshlet_view_resources.second_pass_candidates_buffer, 0, @@ -264,13 +277,13 @@ impl Node for MeshletVisibilityBufferRasterPassNode { &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - visibility_buffer_software_raster_depth_only_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, None, meshlet_view_resources.raster_cluster_rightmost_slot, ); meshlet_view_resources.depth_pyramid.downsample_depth( - "meshlet early shadow downsample depth", + "downsample_depth", render_context, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, @@ -301,7 +314,7 @@ impl Node for MeshletVisibilityBufferRasterPassNode { &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - visibility_buffer_software_raster_depth_only_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, shadow_visibility_buffer_hardware_raster_pipeline, None, meshlet_view_resources.raster_cluster_rightmost_slot, @@ -309,13 +322,12 @@ impl Node for MeshletVisibilityBufferRasterPassNode { resolve_depth( render_context, shadow_view.depth_attachment.get_attachment(StoreOp::Store), - meshlet_view_resources, meshlet_view_bind_groups, resolve_depth_shadow_view_pipeline, camera, ); meshlet_view_resources.depth_pyramid.downsample_depth( - "meshlet late shadow downsample depth", + "downsample_depth", render_context, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, @@ -362,6 +374,29 @@ fn fill_cluster_buffers_pass( ); } +// TODO: Replace this with vkCmdClearColorImage once wgpu supports it +fn clear_visibility_buffer_pass( + render_context: &mut RenderContext, + clear_visibility_buffer_bind_group: &BindGroup, + clear_visibility_buffer_pipeline: &ComputePipeline, + view_size: UVec2, +) { + let command_encoder = render_context.command_encoder(); + let mut clear_visibility_buffer_pass = + command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("clear_visibility_buffer"), + timestamp_writes: None, + }); + clear_visibility_buffer_pass.set_pipeline(clear_visibility_buffer_pipeline); + clear_visibility_buffer_pass.set_push_constants(0, bytemuck::bytes_of(&view_size)); + clear_visibility_buffer_pass.set_bind_group(0, clear_visibility_buffer_bind_group, &[]); + clear_visibility_buffer_pass.dispatch_workgroups( + view_size.x.div_ceil(16), + view_size.y.div_ceil(16), + 1, + ); +} + fn cull_pass( label: &'static str, render_context: &mut RenderContext, @@ -478,7 +513,6 @@ fn raster_pass( fn resolve_depth( render_context: &mut RenderContext, depth_stencil_attachment: RenderPassDepthStencilAttachment, - meshlet_view_resources: &MeshletViewResources, meshlet_view_bind_groups: &MeshletViewBindGroups, resolve_depth_pipeline: &RenderPipeline, camera: &ExtractedCamera, @@ -494,11 +528,6 @@ fn resolve_depth( resolve_pass.set_camera_viewport(viewport); } resolve_pass.set_render_pipeline(resolve_depth_pipeline); - resolve_pass.set_push_constants( - ShaderStages::FRAGMENT, - 0, - &meshlet_view_resources.view_size.x.to_le_bytes(), - ); resolve_pass.set_bind_group(0, &meshlet_view_bind_groups.resolve_depth, &[]); resolve_pass.draw(0..3, 0..1); } @@ -532,11 +561,6 @@ fn resolve_material_depth( resolve_pass.set_camera_viewport(viewport); } resolve_pass.set_render_pipeline(resolve_material_depth_pipeline); - resolve_pass.set_push_constants( - ShaderStages::FRAGMENT, - 0, - &meshlet_view_resources.view_size.x.to_le_bytes(), - ); resolve_pass.set_bind_group(0, resolve_material_depth_bind_group, &[]); resolve_pass.draw(0..3, 0..1); } diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl index f28645013d1ec..4c56c5874ae2f 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_resolve.wgsl @@ -104,8 +104,7 @@ struct VertexOutput { /// Load the visibility buffer texture and resolve it into a VertexOutput. fn resolve_vertex_output(frag_coord: vec4) -> VertexOutput { - let frag_coord_1d = u32(frag_coord.y) * u32(view.viewport.z) + u32(frag_coord.x); - let packed_ids = u32(meshlet_visibility_buffer[frag_coord_1d]); // TODO: Might be faster to load the correct u32 directly + let packed_ids = u32(textureLoad(meshlet_visibility_buffer, vec2(frag_coord.xy)).r); let cluster_id = packed_ids >> 7u; let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id]; var meshlet = meshlets[meshlet_id]; diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl b/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl index 941c31f0939ca..60f6f1b3ea658 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_software_raster.wgsl @@ -167,16 +167,13 @@ fn rasterize_cluster( } fn write_visibility_buffer_pixel(x: f32, y: f32, z: f32, packed_ids: u32) { - let frag_coord_1d = u32(y * view.viewport.z + x); - -#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT let depth = bitcast(z); +#ifdef MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT let visibility = (u64(depth) << 32u) | u64(packed_ids); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], visibility); #else - let depth = bitcast(z); - atomicMax(&meshlet_visibility_buffer[frag_coord_1d], depth); + let visibility = depth; #endif + textureAtomicMax(meshlet_visibility_buffer, vec2(u32(x), u32(y)), visibility); } fn edge_function(a: vec2, b: vec2, c: vec2) -> f32 { diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 48353eb67c609..44d1b960dff52 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -30,8 +30,8 @@ pub enum UvChannel { /// May be created directly from a [`Color`] or an [`Image`]. #[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] #[bind_group_data(StandardMaterialKey)] -#[uniform(0, StandardMaterialUniform)] -#[bindless(16)] +#[uniform(0, StandardMaterialUniform, binding_array(10))] +#[bindless] #[reflect(Default, Debug)] pub struct StandardMaterial { /// The color of the surface of the material before lighting. diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 3bc2dc51e3591..9b3420cbc20b5 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -2,10 +2,11 @@ mod prepass_bindings; use crate::{ alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout, - material_bind_groups::MaterialBindGroupAllocator, queue_material_meshes, - setup_morph_and_skinning_defs, skin, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, - MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, PreparedMaterial, - RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags, RenderMeshInstances, + 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, }; use bevy_app::{App, Plugin, PreUpdate}; @@ -17,12 +18,12 @@ use bevy_render::{ render_resource::binding_types::uniform_buffer, renderer::RenderAdapter, sync_world::RenderEntity, - view::{RenderVisibilityRanges, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT}, - ExtractSchedule, Render, RenderApp, RenderSet, + view::{RenderVisibilityRanges, RetainedViewEntity, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT}, + ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderSet, }; pub use prepass_bindings::*; -use bevy_asset::{load_internal_asset, AssetServer, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prelude::Camera3d, prepass::*, }; @@ -45,7 +46,7 @@ use bevy_render::{ Extract, }; use bevy_transform::prelude::GlobalTransform; -use tracing::error; +use tracing::{error, warn}; #[cfg(feature = "meshlet")] use crate::meshlet::{ @@ -53,17 +54,26 @@ use crate::meshlet::{ MeshletMesh3d, }; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; +use bevy_platform_support::collections::HashMap; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::view::RenderVisibleEntities; +use bevy_render::RenderSet::{PrepareAssets, PrepareResources}; use core::{hash::Hash, marker::PhantomData}; -pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); +pub const PREPASS_SHADER_HANDLE: Handle = + weak_handle!("ce810284-f1ae-4439-ab2e-0d6b204b6284"); pub const PREPASS_BINDINGS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(5533152893177403494); + weak_handle!("3e83537e-ae17-489c-a18a-999bc9c1d252"); -pub const PREPASS_UTILS_SHADER_HANDLE: Handle = Handle::weak_from_u128(4603948296044544); +pub const PREPASS_UTILS_SHADER_HANDLE: Handle = + weak_handle!("02e4643a-a14b-48eb-a339-0c47aeab0d7e"); -pub const PREPASS_IO_SHADER_HANDLE: Handle = Handle::weak_from_u128(81212356509530944); +pub const PREPASS_IO_SHADER_HANDLE: Handle = + weak_handle!("1c065187-c99b-4b7c-ba59-c1575482d2c9"); /// Sets up everything required to use the prepass pipeline. /// @@ -135,11 +145,19 @@ where /// Sets up the prepasses for a [`Material`]. /// /// This depends on the [`PrepassPipelinePlugin`]. -pub struct PrepassPlugin(PhantomData); +pub struct PrepassPlugin { + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, + pub phantom: PhantomData, +} -impl Default for PrepassPlugin { - fn default() -> Self { - Self(Default::default()) +impl PrepassPlugin { + /// Creates a new [`PrepassPlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> Self { + PrepassPlugin { + debug_flags, + phantom: PhantomData, + } } } @@ -165,8 +183,10 @@ where ), ) .add_plugins(( - BinnedRenderPhasePlugin::::default(), - BinnedRenderPhasePlugin::::default(), + BinnedRenderPhasePlugin::::new(self.debug_flags), + BinnedRenderPhasePlugin::::new( + self.debug_flags, + ), )); } @@ -179,22 +199,33 @@ where .add_systems(ExtractSchedule, extract_camera_previous_view_data) .add_systems( Render, - prepare_previous_view_uniforms.in_set(RenderSet::PrepareResources), + prepare_previous_view_uniforms.in_set(PrepareResources), ); } render_app + .init_resource::() + .init_resource::() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_systems( Render, - queue_prepass_material_meshes:: - .in_set(RenderSet::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::), + ( + check_prepass_views_need_specialization.in_set(PrepareAssets), + specialize_prepass_material_meshes:: + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::>) + .after(prepare_assets::) + .after(collect_meshes_for_gpu_building), + queue_prepass_material_meshes:: + .in_set(RenderSet::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::), + ), ); #[cfg(feature = "meshlet")] @@ -226,7 +257,7 @@ pub fn update_previous_view_data( } } -#[derive(Component, Default)] +#[derive(Component, PartialEq, Default)] pub struct PreviousGlobalTransform(pub Affine3A); #[cfg(not(feature = "meshlet"))] @@ -237,15 +268,19 @@ 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), PreviousMeshFilter>, + meshes: Query<(Entity, &GlobalTransform, Option<&PreviousGlobalTransform>), PreviousMeshFilter>, ) { let should_run = views.iter().any(|camera| camera.is_active); if should_run { - for (entity, transform) in &meshes { - commands - .entity(entity) - .try_insert(PreviousGlobalTransform(transform.affine())); + for (entity, transform, old_previous_transform) in &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); + } } } } @@ -771,109 +806,167 @@ pub fn prepare_prepass_view_bind_group( } } -pub fn queue_prepass_material_meshes( - ( - opaque_draw_functions, - alpha_mask_draw_functions, - opaque_deferred_draw_functions, - alpha_mask_deferred_draw_functions, - ): ( - Res>, - Res>, - Res>, - Res>, - ), - prepass_pipeline: Res>, - mut pipelines: ResMut>>, - pipeline_cache: Res, - (render_meshes, render_mesh_instances): ( - Res>, - Res, - ), +/// Stores the [`SpecializedPrepassMaterialViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedPrepassMaterialPipelineCache { + // view_entity -> view pipeline cache + #[deref] + map: HashMap>, + marker: PhantomData, +} + +/// 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)] +pub struct SpecializedPrepassMaterialViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, + marker: PhantomData, +} + +impl Default for SpecializedPrepassMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Default for SpecializedPrepassMaterialViewPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +#[derive(Resource, Deref, DerefMut, Default, Clone)] +pub struct ViewKeyPrepassCache(HashMap); + +#[derive(Resource, Deref, DerefMut, Default, Clone)] +pub struct ViewPrepassSpecializationTicks(HashMap); + +pub fn check_prepass_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + mut views: Query<( + &ExtractedView, + &Msaa, + Option<&DepthPrepass>, + Option<&NormalPrepass>, + Option<&MotionVectorPrepass>, + )>, + ticks: SystemChangeTick, +) { + for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() { + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + if depth_prepass.is_some() { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + if normal_prepass.is_some() { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + if motion_vector_prepass.is_some() { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if let Some(current_key) = view_key_cache.get_mut(&view.retained_view_entity) { + if *current_key != view_key { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } else { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } +} + +pub fn specialize_prepass_material_meshes( + render_meshes: Res>, render_materials: Res>>, + render_mesh_instances: Res, render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, - (mesh_allocator, material_bind_group_allocator): ( - Res, - Res>, - ), - gpu_preprocessing_support: Res, - mut opaque_prepass_render_phases: ResMut>, - mut alpha_mask_prepass_render_phases: ResMut>, - mut opaque_deferred_render_phases: ResMut>, - mut alpha_mask_deferred_render_phases: ResMut>, + material_bind_group_allocator: Res>, + view_key_cache: Res, views: Query<( &ExtractedView, &RenderVisibleEntities, &Msaa, - Option<&DepthPrepass>, - Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, )>, + ( + opaque_prepass_render_phases, + alpha_mask_prepass_render_phases, + opaque_deferred_render_phases, + alpha_mask_deferred_render_phases, + ): ( + Res>, + Res>, + Res>, + Res>, + ), + ( + mut specialized_material_pipeline_cache, + ticks, + prepass_pipeline, + mut pipelines, + pipeline_cache, + view_specialization_ticks, + entity_specialization_ticks, + ): ( + ResMut>, + SystemChangeTick, + Res>, + ResMut>>, + Res, + Res, + Res>, + ), ) where + M: Material, M::Data: PartialEq + Eq + Hash + Clone, { - let opaque_draw_prepass = opaque_draw_functions - .read() - .get_id::>() - .unwrap(); - let alpha_mask_draw_prepass = alpha_mask_draw_functions - .read() - .get_id::>() - .unwrap(); - let opaque_draw_deferred = opaque_deferred_draw_functions - .read() - .get_id::>() - .unwrap(); - let alpha_mask_draw_deferred = alpha_mask_deferred_draw_functions - .read() - .get_id::>() - .unwrap(); - for ( - extracted_view, - visible_entities, - msaa, - depth_prepass, - normal_prepass, - motion_vector_prepass, - deferred_prepass, - ) in &views + for (extracted_view, visible_entities, msaa, motion_vector_prepass, deferred_prepass) in &views { - let ( - mut opaque_phase, - mut alpha_mask_phase, - mut opaque_deferred_phase, - mut alpha_mask_deferred_phase, - ) = ( - opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), - alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), - opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), - alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), - ); - - // Skip if there's no place to put the mesh. - if opaque_phase.is_none() - && alpha_mask_phase.is_none() - && opaque_deferred_phase.is_none() - && alpha_mask_deferred_phase.is_none() + if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) + && !alpha_mask_deferred_render_phases.contains_key(&extracted_view.retained_view_entity) + && !opaque_prepass_render_phases.contains_key(&extracted_view.retained_view_entity) + && !alpha_mask_prepass_render_phases.contains_key(&extracted_view.retained_view_entity) { continue; } - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - if depth_prepass.is_some() { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - if normal_prepass.is_some() { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - if motion_vector_prepass.is_some() { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } + let Some(view_key) = view_key_cache.get(&extracted_view.retained_view_entity) else { + continue; + }; + + let view_tick = view_specialization_ticks + .get(&extracted_view.retained_view_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(extracted_view.retained_view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter::() { + 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; + } - for (render_entity, visible_entity) in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -887,13 +980,14 @@ pub fn queue_prepass_material_meshes( let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group) else { + warn!("Couldn't get bind group for material"); continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let mut mesh_key = view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); + let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); let alpha_mode = material.properties.alpha_mode; match alpha_mode { @@ -976,17 +1070,103 @@ pub fn queue_prepass_material_meshes( } }; + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } +} + +pub fn queue_prepass_material_meshes( + render_mesh_instances: Res, + render_materials: Res>>, + render_material_instances: Res>, + mesh_allocator: Res, + gpu_preprocessing_support: Res, + mut opaque_prepass_render_phases: ResMut>, + mut alpha_mask_prepass_render_phases: ResMut>, + mut opaque_deferred_render_phases: ResMut>, + mut alpha_mask_deferred_render_phases: ResMut>, + views: Query<(&ExtractedView, &RenderVisibleEntities)>, + specialized_material_pipeline_cache: Res>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + for (extracted_view, visible_entities) in &views { + let ( + mut opaque_phase, + mut alpha_mask_phase, + mut opaque_deferred_phase, + mut alpha_mask_deferred_phase, + ) = ( + opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), + alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity), + opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), + alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity), + ); + + let Some(view_specialized_material_pipeline_cache) = + specialized_material_pipeline_cache.get(&extracted_view.retained_view_entity) + else { + continue; + }; + + // Skip if there's no place to put the mesh. + if opaque_phase.is_none() + && alpha_mask_phase.is_none() + && opaque_deferred_phase.is_none() + && alpha_mask_deferred_phase.is_none() + { + continue; + } + + for (render_entity, visible_entity) in visible_entities.iter::() { + let Some((current_change_tick, pipeline_id)) = + view_specialized_material_pipeline_cache.get(visible_entity) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if opaque_phase.as_mut().is_some_and(|phase| { + phase.validate_cached_entity(*visible_entity, *current_change_tick) + }) || alpha_mask_phase.as_mut().is_some_and(|phase| { + phase.validate_cached_entity(*visible_entity, *current_change_tick) + }) || opaque_deferred_phase.as_mut().is_some_and(|phase| { + phase.validate_cached_entity(*visible_entity, *current_change_tick) + }) || alpha_mask_deferred_phase.as_mut().is_some_and(|phase| { + phase.validate_cached_entity(*visible_entity, *current_change_tick) + }) { + 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 { + continue; + }; let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); - match mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD) - { - MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => { + let deferred = match material.properties.render_method { + OpaqueRendererMethod::Forward => false, + OpaqueRendererMethod::Deferred => true, + OpaqueRendererMethod::Auto => unreachable!(), + }; + + match material.properties.render_phase_type { + RenderPhaseType::Opaque => { if deferred { opaque_deferred_phase.as_mut().unwrap().add( OpaqueNoLightmap3dBatchSetKey { - draw_function: opaque_draw_deferred, - pipeline: pipeline_id, + draw_function: material + .properties + .deferred_draw_function_id + .unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -995,18 +1175,23 @@ pub fn queue_prepass_material_meshes( asset_id: mesh_instance.mesh_asset_id.into(), }, (*render_entity, *visible_entity), + mesh_instance.current_uniform_index, BinnedRenderPhaseType::mesh( mesh_instance.should_batch(), &gpu_preprocessing_support, ), + *current_change_tick, ); } else if let Some(opaque_phase) = opaque_phase.as_mut() { let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); opaque_phase.add( OpaqueNoLightmap3dBatchSetKey { - draw_function: opaque_draw_prepass, - pipeline: pipeline_id, + draw_function: material + .properties + .prepass_draw_function_id + .unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -1015,21 +1200,22 @@ pub fn queue_prepass_material_meshes( asset_id: mesh_instance.mesh_asset_id.into(), }, (*render_entity, *visible_entity), + mesh_instance.current_uniform_index, BinnedRenderPhaseType::mesh( mesh_instance.should_batch(), &gpu_preprocessing_support, ), + *current_change_tick, ); } } - // Alpha mask - MeshPipelineKey::MAY_DISCARD => { + RenderPhaseType::AlphaMask => { if deferred { let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_deferred, - pipeline: pipeline_id, + draw_function: material.properties.deferred_draw_function_id.unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -1041,17 +1227,19 @@ pub fn queue_prepass_material_meshes( 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, ); } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let batch_set_key = OpaqueNoLightmap3dBatchSetKey { - draw_function: alpha_mask_draw_prepass, - pipeline: pipeline_id, + draw_function: material.properties.prepass_draw_function_id.unwrap(), + pipeline: *pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, @@ -1063,10 +1251,12 @@ pub fn queue_prepass_material_meshes( 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, ); } } diff --git a/crates/bevy_pbr/src/render/build_indirect_params.wgsl b/crates/bevy_pbr/src/render/build_indirect_params.wgsl index 17152caab0d94..5ca6d4c0ccfff 100644 --- a/crates/bevy_pbr/src/render/build_indirect_params.wgsl +++ b/crates/bevy_pbr/src/render/build_indirect_params.wgsl @@ -12,7 +12,8 @@ IndirectBatchSet, IndirectParametersIndexed, IndirectParametersNonIndexed, - IndirectParametersMetadata, + IndirectParametersCpuMetadata, + IndirectParametersGpuMetadata, MeshInput } @@ -22,26 +23,30 @@ // Data that we use to generate the indirect parameters. // // The `mesh_preprocess.wgsl` shader emits these. -@group(0) @binding(1) var indirect_parameters_metadata: array; +@group(0) @binding(1) var indirect_parameters_cpu_metadata: + array; + +@group(0) @binding(2) var indirect_parameters_gpu_metadata: + array; // Information about each batch set. // // A *batch set* is a set of meshes that might be multi-drawn together. -@group(0) @binding(2) var indirect_batch_sets: array; +@group(0) @binding(3) var indirect_batch_sets: array; #ifdef INDEXED // The buffer of indirect draw parameters that we generate, and that the GPU // reads to issue the draws. // // This buffer is for indexed meshes. -@group(0) @binding(3) var indirect_parameters: +@group(0) @binding(4) var indirect_parameters: array; #else // INDEXED // The buffer of indirect draw parameters that we generate, and that the GPU // reads to issue the draws. // // This buffer is for non-indexed meshes. -@group(0) @binding(3) var indirect_parameters: +@group(0) @binding(4) var indirect_parameters: array; #endif // INDEXED @@ -51,22 +56,21 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { // Figure out our instance index (i.e. batch index). If this thread doesn't // correspond to any index, bail. let instance_index = global_invocation_id.x; - if (instance_index >= arrayLength(&indirect_parameters_metadata)) { + if (instance_index >= arrayLength(&indirect_parameters_cpu_metadata)) { return; } // Unpack the metadata for this batch. - let mesh_index = indirect_parameters_metadata[instance_index].mesh_index; - let base_output_index = indirect_parameters_metadata[instance_index].base_output_index; - let batch_set_index = indirect_parameters_metadata[instance_index].batch_set_index; + let base_output_index = indirect_parameters_cpu_metadata[instance_index].base_output_index; + let batch_set_index = indirect_parameters_cpu_metadata[instance_index].batch_set_index; + let mesh_index = indirect_parameters_gpu_metadata[instance_index].mesh_index; // If we aren't using `multi_draw_indirect_count`, we have a 1:1 fixed // assignment of batches to slots in the indirect parameters buffer, so we // can just use the instance index as the index of our indirect parameters. let early_instance_count = - atomicLoad(&indirect_parameters_metadata[instance_index].early_instance_count); - let late_instance_count = - atomicLoad(&indirect_parameters_metadata[instance_index].late_instance_count); + indirect_parameters_gpu_metadata[instance_index].early_instance_count; + let late_instance_count = indirect_parameters_gpu_metadata[instance_index].late_instance_count; // If in the early phase, we draw only the early meshes. If in the late // phase, we draw only the late meshes. If in the main phase, draw all the @@ -135,4 +139,4 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { indirect_parameters[indirect_parameters_index].vertex_count = current_input[mesh_index].index_count; #endif // INDEXED -} \ No newline at end of file +} diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 02bd6ac7360cf..9394380f7186d 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -1,5 +1,5 @@ use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_color::{ColorToComponents, LinearRgba}; use bevy_ecs::prelude::*; use bevy_math::{Vec3, Vec4}; @@ -127,7 +127,7 @@ pub struct ViewFogUniformOffset { } /// Handle for the fog WGSL Shader internal asset -pub const FOG_SHADER_HANDLE: Handle = Handle::weak_from_u128(4913569193382610166); +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; diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index dfc8739ca4779..fbb54cb1e2871 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -9,46 +9,47 @@ use core::num::{NonZero, NonZeroU64}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, experimental::mip_generation::ViewDepthPyramid, - prepass::{ - DeferredPrepass, DepthPrepass, PreviousViewData, PreviousViewUniformOffset, - PreviousViewUniforms, - }, + prepass::{DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, prelude::resource_exists, - query::{Has, QueryState, With, Without}, + query::{Has, Or, QueryState, With, Without}, resource::Resource, schedule::IntoSystemConfigs as _, system::{lifetimeless::Read, Commands, Query, Res, ResMut}, world::{FromWorld, World}, }; +use bevy_render::batching::gpu_preprocessing::{ + IndirectParametersGpuMetadata, UntypedPhaseIndirectParametersBuffers, +}; use bevy_render::{ batching::gpu_preprocessing::{ BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingSupport, - IndirectBatchSet, IndirectParametersBuffers, IndirectParametersIndexed, - IndirectParametersMetadata, IndirectParametersNonIndexed, + IndirectBatchSet, IndirectParametersBuffers, IndirectParametersCpuMetadata, + IndirectParametersIndexed, IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem, PreprocessWorkItemBuffers, + UntypedPhaseBatchedInstanceBuffers, }, experimental::occlusion_culling::OcclusionCulling, render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, Buffer, BufferBinding, - BufferVec, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, + CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, DynamicBindGroupLayoutEntries, PipelineCache, PushConstantRange, RawBufferVec, Shader, ShaderStages, ShaderType, SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType, UninitBufferVec, }, renderer::{RenderContext, RenderDevice, RenderQueue}, settings::WgpuFeatures, - view::{NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, + view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; @@ -60,20 +61,20 @@ use crate::{ graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform, }; -use super::ViewLightEntities; +use super::{ShadowView, ViewLightEntities}; /// The handle to the `mesh_preprocess.wgsl` compute shader. pub const MESH_PREPROCESS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(16991728318640779533); + 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 = - Handle::weak_from_u128(2720440370122465935); + 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 = - Handle::weak_from_u128(2602194133710559644); + 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 = - Handle::weak_from_u128(3711077208359699672); + weak_handle!("133b01f0-3eaf-4590-9ee9-f0cf91a00b71"); /// The GPU workgroup size. const WORKGROUP_SIZE: usize = 64; @@ -90,6 +91,12 @@ pub struct GpuMeshPreprocessPlugin { pub use_gpu_instance_buffer_builder: bool, } +/// The render node that clears out the GPU-side indirect metadata buffers. +/// +/// This is only used when indirect drawing is enabled. +#[derive(Default)] +pub struct ClearIndirectParametersMetadataNode; + /// The render node for the first mesh preprocessing pass. /// /// This pass runs a compute shader to cull meshes outside the view frustum (if @@ -100,7 +107,7 @@ pub struct GpuMeshPreprocessPlugin { pub struct EarlyGpuPreprocessNode { view_query: QueryState< ( - Entity, + Read, Option>, Option>, Has, @@ -120,13 +127,16 @@ pub struct EarlyGpuPreprocessNode { /// metadata for the subsequent [`LatePrepassBuildIndirectParametersNode`]. pub struct LateGpuPreprocessNode { view_query: QueryState< - (Entity, Read, Read), + ( + Read, + Read, + Read, + ), ( Without, Without, With, With, - Without, ), >, } @@ -136,7 +146,7 @@ pub struct LateGpuPreprocessNode { /// /// This node runs a compute shader on the output of the /// [`EarlyGpuPreprocessNode`] in order to transform the -/// [`IndirectParametersMetadata`] into properly-formatted +/// [`IndirectParametersGpuMetadata`] into properly-formatted /// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. pub struct EarlyPrepassBuildIndirectParametersNode { view_query: QueryState< @@ -144,8 +154,7 @@ pub struct EarlyPrepassBuildIndirectParametersNode { ( Without, Without, - With, - Without, + Or<(With, With)>, ), >, } @@ -156,7 +165,7 @@ pub struct EarlyPrepassBuildIndirectParametersNode { /// /// This node runs a compute shader on the output of the /// [`LateGpuPreprocessNode`] in order to transform the -/// [`IndirectParametersMetadata`] into properly-formatted +/// [`IndirectParametersGpuMetadata`] into properly-formatted /// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. pub struct LatePrepassBuildIndirectParametersNode { view_query: QueryState< @@ -164,9 +173,8 @@ pub struct LatePrepassBuildIndirectParametersNode { ( Without, Without, - With, + Or<(With, With)>, With, - Without, ), >, } @@ -177,7 +185,7 @@ pub struct LatePrepassBuildIndirectParametersNode { /// /// This node runs a compute shader on the output of the /// [`EarlyGpuPreprocessNode`] and [`LateGpuPreprocessNode`] in order to -/// transform the [`IndirectParametersMetadata`] into properly-formatted +/// transform the [`IndirectParametersGpuMetadata`] into properly-formatted /// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. pub struct MainBuildIndirectParametersNode { view_query: QueryState< @@ -389,8 +397,22 @@ pub enum PhasePreprocessBindGroups { /// The bind groups for the compute shaders that reset indirect draw counts and /// build indirect parameters. -#[derive(Resource)] -pub struct BuildIndirectParametersBindGroups { +/// +/// There's one set of bind group for each phase. Phases are keyed off their +/// [`core::any::TypeId`]. +#[derive(Resource, Default, Deref, DerefMut)] +pub struct BuildIndirectParametersBindGroups(pub TypeIdMap); + +impl BuildIndirectParametersBindGroups { + /// Creates a new, empty [`BuildIndirectParametersBindGroups`] table. + pub fn new() -> BuildIndirectParametersBindGroups { + Self::default() + } +} + +/// The per-phase set of bind groups for the compute shaders that reset indirect +/// draw counts and build indirect parameters. +pub struct PhaseBuildIndirectParametersBindGroups { /// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for /// indexed meshes. reset_indexed_indirect_batch_sets: Option, @@ -466,13 +488,18 @@ impl Plugin for GpuMeshPreprocessPlugin { ( prepare_preprocess_pipelines.in_set(RenderSet::Prepare), prepare_preprocess_bind_groups - .run_if( - resource_exists::>, - ) + .run_if(resource_exists::>) .in_set(RenderSet::PrepareBindGroups), write_mesh_culling_data_buffer.in_set(RenderSet::PrepareResourcesFlush), ), ) + .add_render_graph_node::( + Core3d, + NodePbr::ClearIndirectParametersMetadata + ) .add_render_graph_node::(Core3d, NodePbr::EarlyGpuPreprocess) .add_render_graph_node::(Core3d, NodePbr::LateGpuPreprocess) .add_render_graph_node::( @@ -490,28 +517,84 @@ impl Plugin for GpuMeshPreprocessPlugin { .add_render_graph_edges( Core3d, ( + NodePbr::ClearIndirectParametersMetadata, NodePbr::EarlyGpuPreprocess, NodePbr::EarlyPrepassBuildIndirectParameters, Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::EarlyDownsampleDepth, NodePbr::LateGpuPreprocess, NodePbr::LatePrepassBuildIndirectParameters, Node3d::LatePrepass, + Node3d::LateDeferredPrepass, NodePbr::MainBuildIndirectParameters, - // Shadows don't currently support occlusion culling, so we - // treat shadows as effectively the main phase for our - // purposes. - NodePbr::ShadowPass, + Node3d::StartMainPass, ), - ) - .add_render_graph_edge( + ).add_render_graph_edges( Core3d, - NodePbr::MainBuildIndirectParameters, - Node3d::DeferredPrepass + ( + NodePbr::EarlyPrepassBuildIndirectParameters, + NodePbr::EarlyShadowPass, + Node3d::EarlyDownsampleDepth, + ) + ).add_render_graph_edges( + Core3d, + ( + NodePbr::LatePrepassBuildIndirectParameters, + NodePbr::LateShadowPass, + NodePbr::MainBuildIndirectParameters, + ) ); } } +impl Node for ClearIndirectParametersMetadataNode { + fn run<'w>( + &self, + _: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let Some(indirect_parameters_buffers) = world.get_resource::() + else { + return Ok(()); + }; + + // Clear out each indexed and non-indexed GPU-side buffer. + for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() { + if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers + .indexed + .gpu_metadata_buffer() + { + render_context.command_encoder().clear_buffer( + indexed_gpu_metadata_buffer, + 0, + Some( + phase_indirect_parameters_buffers.indexed.batch_count() as u64 + * size_of::() as u64, + ), + ); + } + + if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata_buffer() + { + render_context.command_encoder().clear_buffer( + non_indexed_gpu_metadata_buffer, + 0, + Some( + phase_indirect_parameters_buffers.non_indexed.batch_count() as u64 + * size_of::() as u64, + ), + ); + } + } + + Ok(()) + } +} + impl FromWorld for EarlyGpuPreprocessNode { fn from_world(world: &mut World) -> Self { Self { @@ -534,10 +617,8 @@ impl Node for EarlyGpuPreprocessNode { world: &'w World, ) -> Result<(), NodeRunError> { // Grab the [`BatchedInstanceBuffers`]. - let BatchedInstanceBuffers { - work_item_buffers: ref index_buffers, - .. - } = world.resource::>(); + let batched_instance_buffers = + world.resource::>(); let pipeline_cache = world.resource::(); let preprocess_pipelines = world.resource::(); @@ -579,12 +660,6 @@ impl Node for EarlyGpuPreprocessNode { continue; }; - // Grab the work item buffers for this view. - let Some(phase_work_item_buffers) = index_buffers.get(&view) else { - warn!("The preprocessing index buffer wasn't present"); - continue; - }; - // Select the right pipeline, depending on whether GPU culling is in // use. let maybe_pipeline_id = if no_indirect_drawing { @@ -615,18 +690,26 @@ impl Node for EarlyGpuPreprocessNode { compute_pass.set_pipeline(preprocess_pipeline); // Loop over each render phase. - for (phase_type_id, work_item_buffers) in phase_work_item_buffers { + for (phase_type_id, batched_phase_instance_buffers) in + &batched_instance_buffers.phase_instance_buffers + { + // Grab the work item buffers for this view. + let Some(work_item_buffers) = batched_phase_instance_buffers + .work_item_buffers + .get(&view.retained_view_entity) + else { + continue; + }; + // Fetch the bind group for the render phase. let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else { continue; }; - // If we're drawing indirectly, make sure the mesh preprocessing - // shader has access to the view info it needs to do culling. - let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![]; - if !no_indirect_drawing { - dynamic_offsets.push(view_uniform_offset.offset); - } + // Make sure the mesh preprocessing shader has access to the + // view info it needs to do culling and motion vector + // computation. + let dynamic_offsets = [view_uniform_offset.offset]; // Are we drawing directly or indirectly? match *phase_bind_groups { @@ -770,12 +853,8 @@ impl Node for LateGpuPreprocessNode { world: &'w World, ) -> Result<(), NodeRunError> { // Grab the [`BatchedInstanceBuffers`]. - let BatchedInstanceBuffers { - ref work_item_buffers, - ref late_indexed_indirect_parameters_buffer, - ref late_non_indexed_indirect_parameters_buffer, - .. - } = world.resource::>(); + let batched_instance_buffers = + world.resource::>(); let pipeline_cache = world.resource::(); let preprocess_pipelines = world.resource::(); @@ -790,12 +869,6 @@ impl Node for LateGpuPreprocessNode { // Run the compute passes. for (view, bind_groups, view_uniform_offset) in self.view_query.iter_manual(world) { - // Grab the work item buffers for this view. - let Some(phase_work_item_buffers) = work_item_buffers.get(&view) else { - warn!("The preprocessing index buffer wasn't present"); - continue; - }; - let maybe_pipeline_id = preprocess_pipelines .late_gpu_occlusion_culling_preprocess .pipeline_id; @@ -815,7 +888,25 @@ impl Node for LateGpuPreprocessNode { compute_pass.set_pipeline(preprocess_pipeline); - for (phase_type_id, work_item_buffers) in phase_work_item_buffers { + // Loop over each phase. Because we built the phases in parallel, + // each phase has a separate set of instance buffers. + for (phase_type_id, batched_phase_instance_buffers) in + &batched_instance_buffers.phase_instance_buffers + { + let UntypedPhaseBatchedInstanceBuffers { + ref work_item_buffers, + ref late_indexed_indirect_parameters_buffer, + ref late_non_indexed_indirect_parameters_buffer, + .. + } = *batched_phase_instance_buffers; + + // Grab the work item buffers for this view. + let Some(phase_work_item_buffers) = + work_item_buffers.get(&view.retained_view_entity) + else { + continue; + }; + let ( PreprocessWorkItemBuffers::Indirect { gpu_occlusion_culling: @@ -834,7 +925,7 @@ impl Node for LateGpuPreprocessNode { Some(late_indexed_indirect_parameters_buffer), Some(late_non_indexed_indirect_parameters_buffer), ) = ( - work_item_buffers, + phase_work_item_buffers, bind_groups.get(phase_type_id), late_indexed_indirect_parameters_buffer.buffer(), late_non_indexed_indirect_parameters_buffer.buffer(), @@ -1023,57 +1114,71 @@ fn run_build_indirect_parameters_node( return Ok(()); }; - // Build indexed indirect parameters. - if let ( - Some(reset_indexed_indirect_batch_sets_bind_group), - Some(build_indirect_indexed_params_bind_group), - ) = ( - &build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets, - &build_indirect_params_bind_groups.build_indexed_indirect, - ) { - compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline); - compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]); - let workgroup_count = indirect_parameters_buffers - .batch_set_count(true) - .div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); - } + // Loop over each phase. As each has as separate set of buffers, we need to + // build indirect parameters individually for each phase. + for (phase_type_id, phase_build_indirect_params_bind_groups) in + build_indirect_params_bind_groups.iter() + { + let Some(phase_indirect_parameters_buffers) = + indirect_parameters_buffers.get(phase_type_id) + else { + continue; + }; - compute_pass.set_pipeline(build_indexed_indirect_params_pipeline); - compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]); - let workgroup_count = indirect_parameters_buffers - .indexed_batch_count() - .div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); - } - } + // Build indexed indirect parameters. + if let ( + Some(reset_indexed_indirect_batch_sets_bind_group), + Some(build_indirect_indexed_params_bind_group), + ) = ( + &phase_build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets, + &phase_build_indirect_params_bind_groups.build_indexed_indirect, + ) { + compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline); + compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]); + let workgroup_count = phase_indirect_parameters_buffers + .batch_set_count(true) + .div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + } - // Build non-indexed indirect parameters. - if let ( - Some(reset_non_indexed_indirect_batch_sets_bind_group), - Some(build_indirect_non_indexed_params_bind_group), - ) = ( - &build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets, - &build_indirect_params_bind_groups.build_non_indexed_indirect, - ) { - compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline); - compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]); - let workgroup_count = indirect_parameters_buffers - .batch_set_count(false) - .div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + compute_pass.set_pipeline(build_indexed_indirect_params_pipeline); + compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]); + let workgroup_count = phase_indirect_parameters_buffers + .indexed + .batch_count() + .div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + } } - compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline); - compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]); - let workgroup_count = indirect_parameters_buffers - .non_indexed_batch_count() - .div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + // Build non-indexed indirect parameters. + if let ( + Some(reset_non_indexed_indirect_batch_sets_bind_group), + Some(build_indirect_non_indexed_params_bind_group), + ) = ( + &phase_build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets, + &phase_build_indirect_params_bind_groups.build_non_indexed_indirect, + ) { + compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline); + compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]); + let workgroup_count = phase_indirect_parameters_buffers + .batch_set_count(false) + .div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + } + + compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline); + compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]); + let workgroup_count = phase_indirect_parameters_buffers + .non_indexed + .batch_count() + .div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + } } } @@ -1145,7 +1250,7 @@ impl SpecializedComputePipeline for PreprocessPipeline { type Key = PreprocessPipelineKey; fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor { - let mut shader_defs = vec![]; + let mut shader_defs = vec!["WRITE_INDIRECT_PARAMETERS_METADATA".into()]; if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) { shader_defs.push("INDIRECT".into()); shader_defs.push("FRUSTUM_CULLING".into()); @@ -1311,6 +1416,11 @@ fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntrie DynamicBindGroupLayoutEntries::new_with_indices( ShaderStages::COMPUTE, ( + // `view` + ( + 0, + uniform_buffer::(/* has_dynamic_offset= */ true), + ), // `current_input` (3, storage_buffer_read_only::(false)), // `previous_input` @@ -1323,7 +1433,7 @@ fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntrie ) } -// Returns the first 3 bind group layout entries shared between all invocations +// Returns the first 4 bind group layout entries shared between all invocations // of the indirect parameters building shader. fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries { DynamicBindGroupLayoutEntries::new_with_indices( @@ -1332,9 +1442,13 @@ fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEn (0, storage_buffer_read_only::(false)), ( 1, - storage_buffer_read_only::(false), + storage_buffer_read_only::(false), ), - (2, storage_buffer::(false)), + ( + 2, + storage_buffer_read_only::(false), + ), + (3, storage_buffer::(false)), ), ) } @@ -1345,20 +1459,22 @@ fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries { // GPU culling bind group parameters are a superset of those in the CPU // culling (direct) shader. preprocess_direct_bind_group_layout_entries().extend_with_indices(( - // `indirect_parameters` + // `indirect_parameters_cpu_metadata` ( 7, - storage_buffer::(/* has_dynamic_offset= */ false), + storage_buffer_read_only::( + /* has_dynamic_offset= */ false, + ), ), - // `mesh_culling_data` + // `indirect_parameters_gpu_metadata` ( 8, - storage_buffer_read_only::(/* has_dynamic_offset= */ false), + storage_buffer::(/* has_dynamic_offset= */ false), ), - // `view` + // `mesh_culling_data` ( - 0, - uniform_buffer::(/* has_dynamic_offset= */ true), + 9, + storage_buffer_read_only::(/* has_dynamic_offset= */ false), ), )) } @@ -1619,6 +1735,7 @@ impl BuildIndirectParametersPipeline { )] pub fn prepare_preprocess_bind_groups( mut commands: Commands, + views: Query<(Entity, &ExtractedView)>, view_depth_pyramids: Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>, render_device: Res, batched_instance_buffers: Res>, @@ -1630,18 +1747,14 @@ pub fn prepare_preprocess_bind_groups( ) { // Grab the `BatchedInstanceBuffers`. let BatchedInstanceBuffers { - data_buffer: ref data_buffer_vec, - ref work_item_buffers, current_input_buffer: ref current_input_buffer_vec, previous_input_buffer: ref previous_input_buffer_vec, - ref late_indexed_indirect_parameters_buffer, - ref late_non_indexed_indirect_parameters_buffer, + ref phase_instance_buffers, } = batched_instance_buffers.into_inner(); - let (Some(current_input_buffer), Some(previous_input_buffer), Some(data_buffer)) = ( + let (Some(current_input_buffer), Some(previous_input_buffer)) = ( current_input_buffer_vec.buffer().buffer(), previous_input_buffer_vec.buffer().buffer(), - data_buffer_vec.buffer(), ) else { return; }; @@ -1651,18 +1764,40 @@ pub fn prepare_preprocess_bind_groups( let mut any_indirect = false; // Loop over each view. - for (view, phase_work_item_buffers) in work_item_buffers { + for (view_entity, view) in &views { let mut bind_groups = TypeIdMap::default(); // Loop over each phase. - for (&phase_id, work_item_buffers) in phase_work_item_buffers { + for (phase_type_id, phase_instance_buffers) in phase_instance_buffers { + let UntypedPhaseBatchedInstanceBuffers { + data_buffer: ref data_buffer_vec, + ref work_item_buffers, + ref late_indexed_indirect_parameters_buffer, + ref late_non_indexed_indirect_parameters_buffer, + } = *phase_instance_buffers; + + let Some(data_buffer) = data_buffer_vec.buffer() else { + continue; + }; + + // Grab the indirect parameters buffers for this phase. + let Some(phase_indirect_parameters_buffers) = + indirect_parameters_buffers.get(phase_type_id) + else { + continue; + }; + + let Some(work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) else { + continue; + }; + // Create the `PreprocessBindGroupBuilder`. let preprocess_bind_group_builder = PreprocessBindGroupBuilder { - view: *view, + view: view_entity, late_indexed_indirect_parameters_buffer, late_non_indexed_indirect_parameters_buffer, render_device: &render_device, - indirect_parameters_buffers: &indirect_parameters_buffers, + phase_indirect_parameters_buffers, mesh_culling_data_buffer: &mesh_culling_data_buffer, view_uniforms: &view_uniforms, previous_view_uniforms: &previous_view_uniforms, @@ -1713,13 +1848,13 @@ pub fn prepare_preprocess_bind_groups( // Write that bind group in. if let Some(bind_group) = bind_group { any_indirect = any_indirect || was_indirect; - bind_groups.insert(phase_id, bind_group); + bind_groups.insert(*phase_type_id, bind_group); } } // Save the bind groups. commands - .entity(*view) + .entity(view_entity) .insert(PreprocessBindGroups(bind_groups)); } @@ -1752,7 +1887,7 @@ struct PreprocessBindGroupBuilder<'a> { /// The device. render_device: &'a RenderDevice, /// The buffers that store indirect draw parameters. - indirect_parameters_buffers: &'a IndirectParametersBuffers, + phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers, /// The GPU buffer that stores the information needed to cull each mesh. mesh_culling_data_buffer: &'a MeshCullingDataBuffer, /// The GPU buffer that stores information about the view. @@ -1780,7 +1915,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { /// and GPU occlusion culling are both disabled. fn create_direct_preprocess_bind_groups( &self, - work_item_buffer: &BufferVec, + work_item_buffer: &RawBufferVec, ) -> Option { // Don't use `as_entire_binding()` here; the shader reads the array // length and the underlying buffer may be longer than the actual size @@ -1795,6 +1930,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { "preprocess_direct_bind_group", &self.pipelines.direct_preprocess.bind_group_layout, &BindGroupEntries::with_indices(( + (0, self.view_uniforms.uniforms.binding()?), (3, self.current_input_buffer.as_entire_binding()), (4, self.previous_input_buffer.as_entire_binding()), ( @@ -1816,8 +1952,8 @@ impl<'a> PreprocessBindGroupBuilder<'a> { fn create_indirect_occlusion_culling_preprocess_bind_groups( &self, view_depth_pyramids: &Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>, - indexed_work_item_buffer: &BufferVec, - non_indexed_work_item_buffer: &BufferVec, + indexed_work_item_buffer: &RawBufferVec, + non_indexed_work_item_buffer: &RawBufferVec, gpu_occlusion_culling_work_item_buffers: &GpuOcclusionCullingWorkItemBuffers, ) -> Option { let GpuOcclusionCullingWorkItemBuffers { @@ -1864,7 +2000,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { &self, view_depth_pyramid: &ViewDepthPyramid, previous_view_uniform_offset: &PreviousViewUniformOffset, - indexed_work_item_buffer: &BufferVec, + indexed_work_item_buffer: &RawBufferVec, late_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; @@ -1872,13 +2008,19 @@ impl<'a> PreprocessBindGroupBuilder<'a> { let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; match ( - self.indirect_parameters_buffers.indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .gpu_metadata_buffer(), indexed_work_item_buffer.buffer(), late_indexed_work_item_buffer.buffer(), self.late_indexed_indirect_parameters_buffer.buffer(), ) { ( - Some(indexed_metadata_buffer), + Some(indexed_cpu_metadata_buffer), + Some(indexed_gpu_metadata_buffer), Some(indexed_work_item_gpu_buffer), Some(late_indexed_work_item_gpu_buffer), Some(late_indexed_indirect_parameters_buffer), @@ -1911,8 +2053,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, indexed_cpu_metadata_buffer.as_entire_binding()), + (8, indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), (10, &view_depth_pyramid.all_mips), ( @@ -1955,7 +2098,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { &self, view_depth_pyramid: &ViewDepthPyramid, previous_view_uniform_offset: &PreviousViewUniformOffset, - non_indexed_work_item_buffer: &BufferVec, + non_indexed_work_item_buffer: &RawBufferVec, late_non_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; @@ -1963,14 +2106,19 @@ impl<'a> PreprocessBindGroupBuilder<'a> { let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; match ( - self.indirect_parameters_buffers - .non_indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata_buffer(), non_indexed_work_item_buffer.buffer(), late_non_indexed_work_item_buffer.buffer(), self.late_non_indexed_indirect_parameters_buffer.buffer(), ) { ( - Some(non_indexed_metadata_buffer), + Some(non_indexed_cpu_metadata_buffer), + Some(non_indexed_gpu_metadata_buffer), Some(non_indexed_work_item_gpu_buffer), Some(late_non_indexed_work_item_buffer), Some(late_non_indexed_indirect_parameters_buffer), @@ -2003,8 +2151,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, non_indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), + (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), (10, &view_depth_pyramid.all_mips), ( @@ -2054,12 +2203,18 @@ impl<'a> PreprocessBindGroupBuilder<'a> { let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; match ( - self.indirect_parameters_buffers.indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .gpu_metadata_buffer(), late_indexed_work_item_buffer.buffer(), self.late_indexed_indirect_parameters_buffer.buffer(), ) { ( - Some(indexed_metadata_buffer), + Some(indexed_cpu_metadata_buffer), + Some(indexed_gpu_metadata_buffer), Some(late_indexed_work_item_gpu_buffer), Some(late_indexed_indirect_parameters_buffer), ) => { @@ -2091,8 +2246,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, indexed_cpu_metadata_buffer.as_entire_binding()), + (8, indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), (10, &view_depth_pyramid.all_mips), ( @@ -2134,13 +2290,18 @@ impl<'a> PreprocessBindGroupBuilder<'a> { let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; match ( - self.indirect_parameters_buffers - .non_indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata_buffer(), late_non_indexed_work_item_buffer.buffer(), self.late_non_indexed_indirect_parameters_buffer.buffer(), ) { ( - Some(non_indexed_metadata_buffer), + Some(non_indexed_cpu_metadata_buffer), + Some(non_indexed_gpu_metadata_buffer), Some(non_indexed_work_item_gpu_buffer), Some(late_non_indexed_indirect_parameters_buffer), ) => { @@ -2172,8 +2333,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, non_indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), + (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), (10, &view_depth_pyramid.all_mips), ( @@ -2206,8 +2368,8 @@ impl<'a> PreprocessBindGroupBuilder<'a> { /// is enabled, but GPU occlusion culling is disabled. fn create_indirect_frustum_culling_preprocess_bind_groups( &self, - indexed_work_item_buffer: &BufferVec, - non_indexed_work_item_buffer: &BufferVec, + indexed_work_item_buffer: &RawBufferVec, + non_indexed_work_item_buffer: &RawBufferVec, ) -> Option { Some(PhasePreprocessBindGroups::IndirectFrustumCulling { indexed: self @@ -2222,16 +2384,25 @@ impl<'a> PreprocessBindGroupBuilder<'a> { /// frustum culling is enabled, but GPU occlusion culling is disabled. fn create_indirect_frustum_culling_indexed_bind_group( &self, - indexed_work_item_buffer: &BufferVec, + indexed_work_item_buffer: &RawBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; match ( - self.indirect_parameters_buffers.indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .indexed + .gpu_metadata_buffer(), indexed_work_item_buffer.buffer(), ) { - (Some(indexed_metadata_buffer), Some(indexed_work_item_gpu_buffer)) => { + ( + Some(indexed_cpu_metadata_buffer), + Some(indexed_gpu_metadata_buffer), + Some(indexed_work_item_gpu_buffer), + ) => { // Don't use `as_entire_binding()` here; the shader reads the array // length and the underlying buffer may be longer than the actual size // of the vector. @@ -2260,8 +2431,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, indexed_cpu_metadata_buffer.as_entire_binding()), + (8, indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), )), ), @@ -2275,17 +2447,25 @@ impl<'a> PreprocessBindGroupBuilder<'a> { /// GPU frustum culling is enabled, but GPU occlusion culling is disabled. fn create_indirect_frustum_culling_non_indexed_bind_group( &self, - non_indexed_work_item_buffer: &BufferVec, + non_indexed_work_item_buffer: &RawBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; match ( - self.indirect_parameters_buffers - .non_indexed_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .cpu_metadata_buffer(), + self.phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata_buffer(), non_indexed_work_item_buffer.buffer(), ) { - (Some(non_indexed_metadata_buffer), Some(non_indexed_work_item_gpu_buffer)) => { + ( + Some(non_indexed_cpu_metadata_buffer), + Some(non_indexed_gpu_metadata_buffer), + Some(non_indexed_work_item_gpu_buffer), + ) => { // Don't use `as_entire_binding()` here; the shader reads the array // length and the underlying buffer may be longer than the actual size // of the vector. @@ -2314,8 +2494,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }), ), (6, self.data_buffer.as_entire_binding()), - (7, non_indexed_metadata_buffer.as_entire_binding()), - (8, mesh_culling_data_buffer.as_entire_binding()), + (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), + (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), + (9, mesh_culling_data_buffer.as_entire_binding()), (0, view_uniforms_binding.clone()), )), ), @@ -2334,121 +2515,168 @@ fn create_build_indirect_parameters_bind_groups( render_device: &RenderDevice, pipelines: &PreprocessPipelines, current_input_buffer: &Buffer, - indirect_parameters_buffer: &IndirectParametersBuffers, + indirect_parameters_buffers: &IndirectParametersBuffers, ) { - commands.insert_resource(BuildIndirectParametersBindGroups { - reset_indexed_indirect_batch_sets: match ( - indirect_parameters_buffer.indexed_batch_sets_buffer(), - ) { - (Some(indexed_batch_sets_buffer),) => Some( - render_device.create_bind_group( - "reset_indexed_indirect_batch_sets_bind_group", - // The early bind group is good for the main phase and late - // phase too. They bind the same buffers. - &pipelines - .early_phase - .reset_indirect_batch_sets - .bind_group_layout, - &BindGroupEntries::sequential((indexed_batch_sets_buffer.as_entire_binding(),)), - ), - ), - _ => None, - }, + let mut build_indirect_parameters_bind_groups = BuildIndirectParametersBindGroups::new(); + + for (phase_type_id, phase_indirect_parameters_buffer) in indirect_parameters_buffers.iter() { + build_indirect_parameters_bind_groups.insert( + *phase_type_id, + PhaseBuildIndirectParametersBindGroups { + reset_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer + .indexed + .batch_sets_buffer(),) + { + (Some(indexed_batch_sets_buffer),) => Some( + render_device.create_bind_group( + "reset_indexed_indirect_batch_sets_bind_group", + // The early bind group is good for the main phase and late + // phase too. They bind the same buffers. + &pipelines + .early_phase + .reset_indirect_batch_sets + .bind_group_layout, + &BindGroupEntries::sequential(( + indexed_batch_sets_buffer.as_entire_binding(), + )), + ), + ), + _ => None, + }, - reset_non_indexed_indirect_batch_sets: match ( - indirect_parameters_buffer.non_indexed_batch_sets_buffer(), - ) { - (Some(non_indexed_batch_sets_buffer),) => Some( - render_device.create_bind_group( - "reset_non_indexed_indirect_batch_sets_bind_group", - // The early bind group is good for the main phase and late - // phase too. They bind the same buffers. - &pipelines - .early_phase - .reset_indirect_batch_sets - .bind_group_layout, - &BindGroupEntries::sequential(( - non_indexed_batch_sets_buffer.as_entire_binding(), - )), - ), - ), - _ => None, - }, + reset_non_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer + .non_indexed + .batch_sets_buffer(),) + { + (Some(non_indexed_batch_sets_buffer),) => Some( + render_device.create_bind_group( + "reset_non_indexed_indirect_batch_sets_bind_group", + // The early bind group is good for the main phase and late + // phase too. They bind the same buffers. + &pipelines + .early_phase + .reset_indirect_batch_sets + .bind_group_layout, + &BindGroupEntries::sequential(( + non_indexed_batch_sets_buffer.as_entire_binding(), + )), + ), + ), + _ => None, + }, - build_indexed_indirect: match ( - indirect_parameters_buffer.indexed_metadata_buffer(), - indirect_parameters_buffer.indexed_data_buffer(), - indirect_parameters_buffer.indexed_batch_sets_buffer(), - ) { - ( - Some(indexed_indirect_parameters_metadata_buffer), - Some(indexed_indirect_parameters_data_buffer), - Some(indexed_batch_sets_buffer), - ) => Some( - render_device.create_bind_group( - "build_indexed_indirect_parameters_bind_group", - // The frustum culling bind group is good for occlusion culling - // too. They bind the same buffers. - &pipelines - .gpu_frustum_culling_build_indexed_indirect_params - .bind_group_layout, - &BindGroupEntries::sequential(( - current_input_buffer.as_entire_binding(), - // Don't use `as_entire_binding` here; the shader reads - // the length and `RawBufferVec` overallocates. - BufferBinding { - buffer: indexed_indirect_parameters_metadata_buffer, - offset: 0, - size: NonZeroU64::new( - indirect_parameters_buffer.indexed_batch_count() as u64 - * size_of::() as u64, - ), - }, - indexed_batch_sets_buffer.as_entire_binding(), - indexed_indirect_parameters_data_buffer.as_entire_binding(), - )), - ), - ), - _ => None, - }, + build_indexed_indirect: match ( + phase_indirect_parameters_buffer + .indexed + .cpu_metadata_buffer(), + phase_indirect_parameters_buffer + .indexed + .gpu_metadata_buffer(), + phase_indirect_parameters_buffer.indexed.data_buffer(), + phase_indirect_parameters_buffer.indexed.batch_sets_buffer(), + ) { + ( + Some(indexed_indirect_parameters_cpu_metadata_buffer), + Some(indexed_indirect_parameters_gpu_metadata_buffer), + Some(indexed_indirect_parameters_data_buffer), + Some(indexed_batch_sets_buffer), + ) => Some( + render_device.create_bind_group( + "build_indexed_indirect_parameters_bind_group", + // The frustum culling bind group is good for occlusion culling + // too. They bind the same buffers. + &pipelines + .gpu_frustum_culling_build_indexed_indirect_params + .bind_group_layout, + &BindGroupEntries::sequential(( + current_input_buffer.as_entire_binding(), + // Don't use `as_entire_binding` here; the shader reads + // the length and `RawBufferVec` overallocates. + BufferBinding { + buffer: indexed_indirect_parameters_cpu_metadata_buffer, + offset: 0, + size: NonZeroU64::new( + phase_indirect_parameters_buffer.indexed.batch_count() + as u64 + * size_of::() as u64, + ), + }, + BufferBinding { + buffer: indexed_indirect_parameters_gpu_metadata_buffer, + offset: 0, + size: NonZeroU64::new( + phase_indirect_parameters_buffer.indexed.batch_count() + as u64 + * size_of::() as u64, + ), + }, + indexed_batch_sets_buffer.as_entire_binding(), + indexed_indirect_parameters_data_buffer.as_entire_binding(), + )), + ), + ), + _ => None, + }, - build_non_indexed_indirect: match ( - indirect_parameters_buffer.non_indexed_metadata_buffer(), - indirect_parameters_buffer.non_indexed_data_buffer(), - indirect_parameters_buffer.non_indexed_batch_sets_buffer(), - ) { - ( - Some(non_indexed_indirect_parameters_metadata_buffer), - Some(non_indexed_indirect_parameters_data_buffer), - Some(non_indexed_batch_sets_buffer), - ) => Some( - render_device.create_bind_group( - "build_non_indexed_indirect_parameters_bind_group", - // The frustum culling bind group is good for occlusion culling - // too. They bind the same buffers. - &pipelines - .gpu_frustum_culling_build_non_indexed_indirect_params - .bind_group_layout, - &BindGroupEntries::sequential(( - current_input_buffer.as_entire_binding(), - // Don't use `as_entire_binding` here; the shader reads - // the length and `RawBufferVec` overallocates. - BufferBinding { - buffer: non_indexed_indirect_parameters_metadata_buffer, - offset: 0, - size: NonZeroU64::new( - indirect_parameters_buffer.non_indexed_batch_count() as u64 - * size_of::() as u64, - ), - }, - non_indexed_batch_sets_buffer.as_entire_binding(), - non_indexed_indirect_parameters_data_buffer.as_entire_binding(), - )), - ), - ), - _ => None, - }, - }); + build_non_indexed_indirect: match ( + phase_indirect_parameters_buffer + .non_indexed + .cpu_metadata_buffer(), + phase_indirect_parameters_buffer + .non_indexed + .gpu_metadata_buffer(), + phase_indirect_parameters_buffer.non_indexed.data_buffer(), + phase_indirect_parameters_buffer + .non_indexed + .batch_sets_buffer(), + ) { + ( + Some(non_indexed_indirect_parameters_cpu_metadata_buffer), + Some(non_indexed_indirect_parameters_gpu_metadata_buffer), + Some(non_indexed_indirect_parameters_data_buffer), + Some(non_indexed_batch_sets_buffer), + ) => Some( + render_device.create_bind_group( + "build_non_indexed_indirect_parameters_bind_group", + // The frustum culling bind group is good for occlusion culling + // too. They bind the same buffers. + &pipelines + .gpu_frustum_culling_build_non_indexed_indirect_params + .bind_group_layout, + &BindGroupEntries::sequential(( + current_input_buffer.as_entire_binding(), + // Don't use `as_entire_binding` here; the shader reads + // the length and `RawBufferVec` overallocates. + BufferBinding { + buffer: non_indexed_indirect_parameters_cpu_metadata_buffer, + offset: 0, + size: NonZeroU64::new( + phase_indirect_parameters_buffer.non_indexed.batch_count() + as u64 + * size_of::() as u64, + ), + }, + BufferBinding { + buffer: non_indexed_indirect_parameters_gpu_metadata_buffer, + offset: 0, + size: NonZeroU64::new( + phase_indirect_parameters_buffer.non_indexed.batch_count() + as u64 + * size_of::() as u64, + ), + }, + non_indexed_batch_sets_buffer.as_entire_binding(), + non_indexed_indirect_parameters_data_buffer.as_entire_binding(), + )), + ), + ), + _ => None, + }, + }, + ); + } + + commands.insert_resource(build_indirect_parameters_bind_groups); } /// Writes the information needed to do GPU mesh culling to the GPU. diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index ff4c0b0ac8d48..5dc225d4662bc 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -5,6 +5,8 @@ use bevy_asset::UntypedAssetId; use bevy_color::ColorToComponents; use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; 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}, prelude::*, @@ -12,6 +14,11 @@ use bevy_ecs::{ }; 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_render::experimental::occlusion_culling::{ + OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, +}; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::SortedCameras, @@ -37,7 +44,7 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::default; -use core::{hash::Hash, ops::Range}; +use core::{hash::Hash, marker::PhantomData, ops::Range}; #[cfg(feature = "trace")] use tracing::info_span; use tracing::{error, warn}; @@ -78,6 +85,8 @@ pub struct ExtractedDirectionalLight { pub frusta: EntityHashMap>, pub render_layers: RenderLayers, pub soft_shadow_size: Option, + /// True if this light is using two-phase occlusion culling. + pub occlusion_culling: bool, } // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl! @@ -250,6 +259,7 @@ pub fn extract_lights( &ViewVisibility, Option<&RenderLayers>, Option<&VolumetricLight>, + Has, ), Without, >, @@ -414,6 +424,7 @@ pub fn extract_lights( view_visibility, maybe_layers, volumetric_light, + occlusion_culling, ) in &directional_lights { if !view_visibility.get() { @@ -479,6 +490,7 @@ pub fn extract_lights( cascades: extracted_cascades, frusta: extracted_frusta, render_layers: maybe_layers.unwrap_or_default().clone(), + occlusion_culling, }, RenderCascadesVisibleEntities { entities: cascade_visible_entities, @@ -1062,7 +1074,7 @@ pub fn prepare_lights( // NOTE: iOS Simulator is missing CubeArray support so we use Cube instead. // See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added. #[cfg(all( - not(feature = "ios_simulator"), + not(target_abi = "sim"), any( not(feature = "webgl"), not(target_arch = "wasm32"), @@ -1071,10 +1083,11 @@ pub fn prepare_lights( ))] dimension: Some(TextureViewDimension::CubeArray), #[cfg(any( - feature = "ios_simulator", + target_abi = "sim", all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")) ))] dimension: Some(TextureViewDimension::Cube), + usage: None, aspect: TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, @@ -1118,6 +1131,7 @@ pub fn prepare_lights( dimension: Some(TextureViewDimension::D2Array), #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, @@ -1142,7 +1156,9 @@ pub fn prepare_lights( .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok()) { live_views.insert(entity); + let mut view_lights = Vec::new(); + let mut view_occlusion_culling_lights = Vec::new(); let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing { GpuPreprocessingMode::Culling @@ -1240,6 +1256,7 @@ pub fn prepare_lights( label: Some("point_light_shadow_map_texture_view"), format: None, dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::All, base_mip_level: 0, mip_level_count: None, @@ -1296,7 +1313,7 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map shadow_render_phases - .insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(retained_view_entity); } } @@ -1341,6 +1358,7 @@ pub fn prepare_lights( label: Some("spot_light_shadow_map_texture_view"), format: None, dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::All, base_mip_level: 0, mip_level_count: None, @@ -1393,7 +1411,8 @@ pub fn prepare_lights( if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + shadow_render_phases + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(retained_view_entity); } } @@ -1474,6 +1493,7 @@ pub fn prepare_lights( label: Some("directional_light_shadow_map_array_texture_view"), format: None, dimension: Some(TextureViewDimension::D2), + usage: None, aspect: TextureAspect::All, base_mip_level: 0, mip_level_count: None, @@ -1484,7 +1504,7 @@ pub fn prepare_lights( // NOTE: For point and spotlights, we reuse the same depth attachment for all views. // However, for directional lights, we want a new depth attachment for each view, // so that the view is cleared for each view. - let depth_attachment = DepthAttachment::new(depth_texture_view, Some(0.0)); + let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0)); directional_depth_texture_array_index += 1; @@ -1533,10 +1553,23 @@ pub fn prepare_lights( view_lights.push(view_light_entity); + // If this light is using occlusion culling, add the appropriate components. + if light.occlusion_culling { + commands.entity(view_light_entity).insert(( + OcclusionCulling, + OcclusionCullingSubview { + depth_texture_view, + depth_texture_size: directional_light_shadow_map.size as u32, + }, + )); + view_occlusion_culling_lights.push(view_light_entity); + } + // Subsequent views with the same light entity will **NOT** reuse the same shadow map // (Because the cascades are unique to each view) // TODO: Implement GPU culling for shadow passes. - shadow_render_phases.insert_or_clear(retained_view_entity, gpu_preprocessing_mode); + shadow_render_phases + .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(retained_view_entity); } } @@ -1555,6 +1588,16 @@ pub fn prepare_lights( offset: view_gpu_lights_writer.write(&gpu_lights), }, )); + + // Make a link from the camera to all shadow cascades with occlusion + // culling enabled. + if !view_occlusion_culling_lights.is_empty() { + commands + .entity(entity) + .insert(OcclusionCullingSubviewEntities( + view_occlusion_culling_lights, + )); + } } // Despawn light-view entities for views that no longer exist @@ -1580,25 +1623,119 @@ fn despawn_entities(commands: &mut Commands, entities: Vec) { }); } -/// For each shadow cascade, iterates over all the meshes "visible" from it and -/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as -/// appropriate. -pub fn queue_shadows( - shadow_draw_functions: Res>, +// These will be extracted in the material extraction, which will also clear the needs_specialization +// collection. +pub fn check_light_entities_needing_specialization( + needs_specialization: Query>, Changed)>, + mut entities_needing_specialization: ResMut>, + mut removed_components: RemovedComponents, +) { + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } + + for removed in removed_components.read() { + entities_needing_specialization.entities.push(removed); + } +} + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct LightKeyCache(HashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct LightSpecializationTicks(HashMap); + +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedShadowMaterialPipelineCache { + // view light entity -> view pipeline cache + #[deref] + map: HashMap>, + marker: PhantomData, +} + +#[derive(Deref, DerefMut)] +pub struct SpecializedShadowMaterialViewPipelineCache { + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, + marker: PhantomData, +} + +impl Default for SpecializedShadowMaterialPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Default for SpecializedShadowMaterialViewPipelineCache { + fn default() -> Self { + Self { + map: MainEntityHashMap::default(), + marker: PhantomData, + } + } +} + +pub fn check_views_lights_need_specialization( + view_lights: Query<&ViewLightEntities, With>, + view_light_entities: Query<(&LightEntity, &ExtractedView)>, + shadow_render_phases: Res>, + mut light_key_cache: ResMut, + mut light_specialization_ticks: ResMut, + ticks: SystemChangeTick, +) { + for view_lights in &view_lights { + for view_light_entity in view_lights.lights.iter().copied() { + let Ok((light_entity, extracted_view_light)) = + view_light_entities.get(view_light_entity) + else { + continue; + }; + if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { + continue; + } + + let is_directional_light = matches!(light_entity, LightEntity::Directional { .. }); + let mut light_key = MeshPipelineKey::DEPTH_PREPASS; + light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light); + if let Some(current_key) = + light_key_cache.get_mut(&extracted_view_light.retained_view_entity) + { + if *current_key != light_key { + light_key_cache.insert(extracted_view_light.retained_view_entity, light_key); + light_specialization_ticks + .insert(extracted_view_light.retained_view_entity, ticks.this_run()); + } + } else { + light_key_cache.insert(extracted_view_light.retained_view_entity, light_key); + light_specialization_ticks + .insert(extracted_view_light.retained_view_entity, ticks.this_run()); + } + } + } +} + +pub fn specialize_shadows( prepass_pipeline: Res>, - (render_meshes, render_mesh_instances, render_materials, render_material_instances): ( + ( + render_meshes, + render_mesh_instances, + render_materials, + render_material_instances, + material_bind_group_allocator, + ): ( Res>, Res, Res>>, Res>, + Res>, ), - material_bind_group_allocator: Res>, - mut shadow_render_phases: ResMut>, + shadow_render_phases: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, render_lightmaps: Res, - gpu_preprocessing_support: Res, - mesh_allocator: Res, view_lights: Query<(Entity, &ViewLightEntities), With>, view_light_entities: Query<(&LightEntity, &ExtractedView)>, point_light_entities: Query<&RenderCubemapVisibleEntities, With>, @@ -1607,24 +1744,36 @@ pub fn queue_shadows( With, >, spot_light_entities: Query<&RenderVisibleMeshEntities, With>, + light_key_cache: Res, + mut specialized_material_pipeline_cache: ResMut>, + light_specialization_ticks: Res, + entity_specialization_ticks: Res>, + ticks: SystemChangeTick, ) where M::Data: PartialEq + Eq + Hash + Clone, { + // Record the retained IDs of all shadow views so that we can expire old + // pipeline IDs. + let mut all_shadow_views: HashSet = HashSet::default(); + for (entity, view_lights) in &view_lights { - let draw_shadow_mesh = shadow_draw_functions.read().id::>(); for view_light_entity in view_lights.lights.iter().copied() { let Ok((light_entity, extracted_view_light)) = view_light_entities.get(view_light_entity) else { continue; }; - let Some(shadow_phase) = - shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity) + + all_shadow_views.insert(extracted_view_light.retained_view_entity); + + if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) { + continue; + } + let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity) else { continue; }; - let is_directional_light = matches!(light_entity, LightEntity::Directional { .. }); let visible_entities = match light_entity { LightEntity::Directional { light_entity, @@ -1648,14 +1797,32 @@ pub fn queue_shadows( .get(*light_entity) .expect("Failed to get spot light visible entities"), }; - let mut light_key = MeshPipelineKey::DEPTH_PREPASS; - light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light); // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued - for (entity, main_entity) in visible_entities.iter().copied() { - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) + let view_tick = light_specialization_ticks + .get(&extracted_view_light.retained_view_entity) + .unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(extracted_view_light.retained_view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter().copied() { + 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_instance) = + render_mesh_instances.render_mesh_queue_data(visible_entity) else { continue; }; @@ -1665,7 +1832,7 @@ pub fn queue_shadows( { continue; } - let Some(material_asset_id) = render_material_instances.get(&main_entity) else { + let Some(material_asset_id) = render_material_instances.get(&visible_entity) else { continue; }; let Some(material) = render_materials.get(*material_asset_id) else { @@ -1681,14 +1848,17 @@ pub fn queue_shadows( }; let mut mesh_key = - light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); + *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()); // Even though we don't use the lightmap in the shadow map, the // `SetMeshBindGroup` render command will bind the data for it. So // we need to include the appropriate flag in the mesh pipeline key // to ensure that the necessary bind group layout entries are // present. - if render_lightmaps.render_lightmaps.contains_key(&main_entity) { + if render_lightmaps + .render_lightmaps + .contains_key(&visible_entity) + { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } @@ -1720,12 +1890,120 @@ pub fn queue_shadows( } }; + 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(|view, _| all_shadow_views.contains(view)); +} + +/// For each shadow cascade, iterates over all the meshes "visible" from it and +/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as +/// appropriate. +pub fn queue_shadows( + shadow_draw_functions: Res>, + render_mesh_instances: Res, + render_materials: Res>>, + render_material_instances: Res>, + mut shadow_render_phases: ResMut>, + gpu_preprocessing_support: Res, + mesh_allocator: Res, + view_lights: Query<(Entity, &ViewLightEntities), With>, + view_light_entities: Query<(&LightEntity, &ExtractedView)>, + point_light_entities: Query<&RenderCubemapVisibleEntities, With>, + directional_light_entities: Query< + &RenderCascadesVisibleEntities, + With, + >, + spot_light_entities: Query<&RenderVisibleMeshEntities, With>, + specialized_material_pipeline_cache: Res>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + let draw_shadow_mesh = shadow_draw_functions.read().id::>(); + for (entity, view_lights) in &view_lights { + for view_light_entity in view_lights.lights.iter().copied() { + let Ok((light_entity, extracted_view_light)) = + view_light_entities.get(view_light_entity) + else { + continue; + }; + let Some(shadow_phase) = + shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity) + else { + continue; + }; + + let Some(view_specialized_material_pipeline_cache) = + specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity) + else { + continue; + }; + + let visible_entities = match light_entity { + LightEntity::Directional { + light_entity, + cascade_index, + } => directional_light_entities + .get(*light_entity) + .expect("Failed to get directional light visible entities") + .entities + .get(&entity) + .expect("Failed to get directional light visible entities for view") + .get(*cascade_index) + .expect("Failed to get directional light visible entities for cascade"), + LightEntity::Point { + light_entity, + face_index, + } => point_light_entities + .get(*light_entity) + .expect("Failed to get point light visible entities") + .get(*face_index), + LightEntity::Spot { light_entity } => spot_light_entities + .get(*light_entity) + .expect("Failed to get spot light visible entities"), + }; + + for (entity, main_entity) in visible_entities.iter().copied() { + let Some((current_change_tick, pipeline_id)) = + view_specialized_material_pipeline_cache.get(&main_entity) + else { + continue; + }; + + // Skip the entity if it's cached in a bin and up to date. + if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) { + continue; + } + + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) + else { + continue; + }; + if !mesh_instance + .flags + .contains(RenderMeshInstanceFlags::SHADOW_CASTER) + { + continue; + } + + let Some(material_asset_id) = render_material_instances.get(&main_entity) else { + continue; + }; + 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); let batch_set_key = ShadowBatchSetKey { - pipeline: pipeline_id, + pipeline: *pipeline_id, draw_function: draw_shadow_mesh, + material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, }; @@ -1736,10 +2014,12 @@ pub fn queue_shadows( asset_id: mesh_instance.mesh_asset_id.into(), }, (entity, main_entity), + mesh_instance.current_uniform_index, BinnedRenderPhaseType::mesh( mesh_instance.should_batch(), &gpu_preprocessing_support, ), + *current_change_tick, ); } } @@ -1772,6 +2052,11 @@ pub struct ShadowBatchSetKey { /// The function used to draw. pub draw_function: DrawFunctionId, + /// The ID of a bind group specific to the material. + /// + /// In the case of PBR, this is the `MaterialBindGroupIndex`. + pub material_bind_group_index: Option, + /// 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 @@ -1862,13 +2147,44 @@ impl CachedRenderPipelinePhaseItem for Shadow { } } +/// The rendering node that renders meshes that were "visible" (so to speak) +/// from a light last frame. +/// +/// If occlusion culling for a light is disabled, then this node simply renders +/// all meshes in range of the light. +#[derive(Deref, DerefMut)] +pub struct EarlyShadowPassNode(ShadowPassNode); + +/// The rendering node that renders meshes that became newly "visible" (so to +/// speak) from a light this frame. +/// +/// If occlusion culling for a light is disabled, then this node does nothing. +#[derive(Deref, DerefMut)] +pub struct LateShadowPassNode(ShadowPassNode); + +/// Encapsulates rendering logic shared between the early and late shadow pass +/// nodes. pub struct ShadowPassNode { + /// The query that finds cameras in which shadows are visible. main_view_query: QueryState>, - view_light_query: QueryState<(Read, Read)>, + /// The query that finds shadow cascades. + view_light_query: QueryState<(Read, Read, Has)>, } -impl ShadowPassNode { - pub fn new(world: &mut World) -> Self { +impl FromWorld for EarlyShadowPassNode { + fn from_world(world: &mut World) -> Self { + Self(ShadowPassNode::from_world(world)) + } +} + +impl FromWorld for LateShadowPassNode { + fn from_world(world: &mut World) -> Self { + Self(ShadowPassNode::from_world(world)) + } +} + +impl FromWorld for ShadowPassNode { + fn from_world(world: &mut World) -> Self { Self { main_view_query: QueryState::new(world), view_light_query: QueryState::new(world), @@ -1876,17 +2192,52 @@ impl ShadowPassNode { } } -impl Node for ShadowPassNode { +impl Node for EarlyShadowPassNode { + fn update(&mut self, world: &mut World) { + self.0.update(world); + } + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + ) -> Result<(), NodeRunError> { + self.0.run(graph, render_context, world, false) + } +} + +impl Node for LateShadowPassNode { + fn update(&mut self, world: &mut World) { + self.0.update(world); + } + + fn run<'w>( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + world: &'w World, + ) -> Result<(), NodeRunError> { + self.0.run(graph, render_context, world, true) + } +} + +impl ShadowPassNode { fn update(&mut self, world: &mut World) { self.main_view_query.update_archetypes(world); self.view_light_query.update_archetypes(world); } + /// Runs the node logic. + /// + /// `is_late` is true if this is the late shadow pass or false if this is + /// the early shadow pass. fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, world: &'w World, + is_late: bool, ) -> Result<(), NodeRunError> { let diagnostics = render_context.diagnostic_recorder(); @@ -1901,12 +2252,18 @@ impl Node for ShadowPassNode { if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) { for view_light_entity in view_lights.lights.iter().copied() { - let Ok((view_light, extracted_light_view)) = + let Ok((view_light, extracted_light_view, occlusion_culling)) = self.view_light_query.get_manual(world, view_light_entity) else { continue; }; + // There's no need for a late shadow pass if the light isn't + // using occlusion culling. + if is_late && !occlusion_culling { + continue; + } + let Some(shadow_phase) = shadow_render_phases.get(&extracted_light_view.retained_view_entity) else { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 33ccbcc9afb03..fb9737f7ef2e8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,5 +1,3 @@ -use core::mem::size_of; - use crate::material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot}; use allocator::MeshAllocator; use bevy_asset::{load_internal_asset, AssetId, UntypedAssetId}; @@ -10,9 +8,10 @@ use bevy_core_pipeline::{ prepass::MotionVectorPrepass, }; use bevy_derive::{Deref, DerefMut}; +use bevy_diagnostic::FrameCount; use bevy_ecs::{ prelude::*, - query::ROQueryItem, + query::{QueryData, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; @@ -22,34 +21,40 @@ use bevy_render::{ batching::{ gpu_preprocessing::{ self, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, - IndirectParametersIndexed, IndirectParametersMetadata, IndirectParametersNonIndexed, - InstanceInputUniformBuffer, + IndirectParametersCpuMetadata, IndirectParametersIndexed, IndirectParametersNonIndexed, + InstanceInputUniformBuffer, UntypedPhaseIndirectParametersBuffers, }, no_gpu_preprocessing, GetBatchData, GetFullBatchData, NoAutomaticBatching, }, camera::Camera, - mesh::*, + mesh::{skinning::SkinnedMesh, *}, primitives::Aabb, render_asset::RenderAssets, render_phase::{ - BinnedRenderPhasePlugin, PhaseItem, PhaseItemExtraIndex, RenderCommand, + BinnedRenderPhasePlugin, InputUniformIndex, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SortedRenderPhasePlugin, TrackedRenderPass, }, render_resource::*, renderer::{RenderAdapter, RenderDevice, RenderQueue}, + sync_world::MainEntityHashSet, texture::DefaultImageSampler, view::{ - self, NoFrustumCulling, NoIndirectDrawing, RenderVisibilityRanges, ViewTarget, - ViewUniformOffset, ViewVisibility, VisibilityRange, + self, NoFrustumCulling, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, + ViewTarget, ViewUniformOffset, ViewVisibility, VisibilityRange, }, Extract, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{default, Parallel}; +use bevy_utils::{default, Parallel, TypeIdMap}; +use core::any::TypeId; +use core::mem::size_of; use material_bind_groups::MaterialBindingId; -use render::skin::{self, SkinIndex}; +use render::skin; use tracing::{error, warn}; +use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; +use crate::environment_map::EnvironmentMapLight; +use crate::irradiance_volume::IrradianceVolume; use crate::{ render::{ morph::{ @@ -60,34 +65,58 @@ use crate::{ }, *, }; +use bevy_core_pipeline::core_3d::Camera3d; +use bevy_core_pipeline::oit::OrderIndependentTransparencySettings; +use bevy_core_pipeline::prepass::{DeferredPrepass, DepthPrepass, NormalPrepass}; +use bevy_core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; +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 bytemuck::{Pod, Zeroable}; use nonmax::{NonMaxU16, NonMaxU32}; use smallvec::{smallvec, SmallVec}; use static_assertions::const_assert_eq; -use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; - /// Provides support for rendering 3D meshes. -#[derive(Default)] pub struct MeshRenderPlugin { /// Whether we're building [`MeshUniform`]s on GPU. /// /// This requires compute shader support and so will be forcibly disabled if /// the platform doesn't support those. pub use_gpu_instance_buffer_builder: bool, + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, } -pub const FORWARD_IO_HANDLE: Handle = Handle::weak_from_u128(2645551199423808407); -pub const MESH_VIEW_TYPES_HANDLE: Handle = Handle::weak_from_u128(8140454348013264787); -pub const MESH_VIEW_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(9076678235888822571); -pub const MESH_TYPES_HANDLE: Handle = Handle::weak_from_u128(2506024101911992377); -pub const MESH_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(16831548636314682308); -pub const MESH_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(6300874327833745635); -pub const MESH_SHADER_HANDLE: Handle = Handle::weak_from_u128(3252377289100772450); -pub const SKINNING_HANDLE: Handle = Handle::weak_from_u128(13215291596265391738); -pub const MORPH_HANDLE: Handle = Handle::weak_from_u128(970982813587607345); -pub const OCCLUSION_CULLING_HANDLE: Handle = Handle::weak_from_u128(285365001154292827); +impl MeshRenderPlugin { + /// Creates a new [`MeshRenderPlugin`] with the given debug flags. + pub fn new(debug_flags: RenderDebugFlags) -> MeshRenderPlugin { + MeshRenderPlugin { + use_gpu_instance_buffer_builder: false, + debug_flags, + } + } +} + +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 @@ -151,19 +180,17 @@ impl Plugin for MeshRenderPlugin { (no_automatic_skin_batching, no_automatic_morph_batching), ) .add_plugins(( - BinnedRenderPhasePlugin::::default(), - BinnedRenderPhasePlugin::::default(), - BinnedRenderPhasePlugin::::default(), - BinnedRenderPhasePlugin::::default(), - BinnedRenderPhasePlugin::::default(), - SortedRenderPhasePlugin::::default(), - SortedRenderPhasePlugin::::default(), + BinnedRenderPhasePlugin::::new(self.debug_flags), + BinnedRenderPhasePlugin::::new(self.debug_flags), + BinnedRenderPhasePlugin::::new(self.debug_flags), + BinnedRenderPhasePlugin::::new(self.debug_flags), + BinnedRenderPhasePlugin::::new(self.debug_flags), + SortedRenderPhasePlugin::::new(self.debug_flags), + SortedRenderPhasePlugin::::new(self.debug_flags), )); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app - .init_resource::() - .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -187,7 +214,7 @@ impl Plugin for MeshRenderPlugin { set_mesh_motion_vector_flags.in_set(RenderSet::PrepareMeshes), prepare_skins.in_set(RenderSet::PrepareResources), prepare_morphs.in_set(RenderSet::PrepareResources), - prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups), + prepare_mesh_bind_groups.in_set(RenderSet::PrepareBindGroups), prepare_mesh_view_bind_groups .in_set(RenderSet::PrepareBindGroups) .after(prepare_oit_buffers), @@ -204,8 +231,14 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() + .init_resource::() .init_resource::() - .init_resource::(); + .init_resource::() + .add_systems( + Render, + check_views_need_specialization.in_set(PrepareAssets), + ); let gpu_preprocessing_support = render_app.world().resource::(); @@ -217,12 +250,15 @@ impl Plugin for MeshRenderPlugin { if use_gpu_instance_buffer_builder { render_app - .init_resource::>() + .init_resource::>() .init_resource::() + .init_resource::() .add_systems( ExtractSchedule, - extract_meshes_for_gpu_building - .in_set(ExtractMeshesSet), + extract_meshes_for_gpu_building.in_set(ExtractMeshesSet), ) .add_systems( Render, @@ -283,6 +319,141 @@ impl Plugin for MeshRenderPlugin { } } +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewKeyCache(HashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewSpecializationTicks(HashMap); + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + mut views: Query<( + &ExtractedView, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + Option<&ShadowFilteringMethod>, + Has, + ( + Has, + Has, + Has, + Has, + ), + Option<&Camera3d>, + Has, + Option<&Projection>, + Has, + ( + Has>, + Has>, + ), + Has, + )>, + ticks: SystemChangeTick, +) { + for ( + view, + msaa, + tonemapping, + dither, + shadow_filter_method, + ssao, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), + camera_3d, + temporal_jitter, + projection, + distance_fog, + (has_environment_maps, has_irradiance_volumes), + has_oit, + ) in views.iter_mut() + { + let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) + | MeshPipelineKey::from_hdr(view.hdr); + + if normal_prepass { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if deferred_prepass { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + + if temporal_jitter { + view_key |= MeshPipelineKey::TEMPORAL_JITTER; + } + + if has_environment_maps { + view_key |= MeshPipelineKey::ENVIRONMENT_MAP; + } + + if has_irradiance_volumes { + view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; + } + + if has_oit { + view_key |= MeshPipelineKey::OIT_ENABLED; + } + + if let Some(projection) = projection { + view_key |= match projection { + Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, + Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, + Projection::Custom(_) => MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD, + }; + } + + match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { + ShadowFilteringMethod::Hardware2x2 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; + } + ShadowFilteringMethod::Gaussian => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; + } + ShadowFilteringMethod::Temporal => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; + } + } + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + view_key |= tonemapping_pipeline_key(*tonemapping); + } + if let Some(DebandDither::Enabled) = dither { + view_key |= MeshPipelineKey::DEBAND_DITHER; + } + } + if ssao { + view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; + } + if distance_fog { + view_key |= MeshPipelineKey::DISTANCE_FOG; + } + if let Some(camera_3d) = camera_3d { + view_key |= screen_space_specular_transmission_pipeline_key( + camera_3d.screen_space_specular_transmission_quality, + ); + } + if !view_key_cache + .get_mut(&view.retained_view_entity) + .is_some_and(|current_key| *current_key == view_key) + { + view_key_cache.insert(view.retained_view_entity, view_key); + view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run()); + } + } +} + #[derive(Component)] pub struct MeshTransforms { pub world_from_local: Affine3, @@ -320,13 +491,15 @@ pub struct MeshUniform { pub first_vertex_index: u32, /// The current skin index, or `u32::MAX` if there's no skin. pub current_skin_index: u32, - /// The previous skin index, or `u32::MAX` if there's no previous skin. - pub previous_skin_index: u32, /// The material and lightmap indices, packed into 32 bits. /// /// Low 16 bits: index of the material inside the bind group data. /// High 16 bits: index of the lightmap in the binding array. pub material_and_lightmap_bind_group_slot: u32, + /// User supplied tag to identify this mesh instance. + pub tag: u32, + /// Padding. + pub pad: u32, } /// Information that has to be transferred from CPU to GPU in order to produce @@ -376,17 +549,23 @@ pub struct MeshInputUniform { pub index_count: u32, /// The current skin index, or `u32::MAX` if there's no skin. pub current_skin_index: u32, - /// The previous skin index, or `u32::MAX` if there's no previous skin. - pub previous_skin_index: u32, /// The material and lightmap indices, packed into 32 bits. /// /// Low 16 bits: index of the material inside the bind group data. /// High 16 bits: index of the lightmap in the binding array. pub material_and_lightmap_bind_group_slot: u32, + /// The number of the frame on which this [`MeshInputUniform`] was built. + /// + /// This is used to validate the previous transform and skin. If this + /// [`MeshInputUniform`] wasn't updated on this frame, then we know that + /// neither this mesh's transform nor that of its joints have been updated + /// on this frame, and therefore the transforms of both this mesh and its + /// joints must be identical to those for the previous frame. + pub timestamp: u32, + /// User supplied tag to identify this mesh instance. + pub tag: u32, /// Padding. - pub pad_a: u32, - /// Padding. - pub pad_b: u32, + pub pad: u32, } /// Information about each mesh instance needed to cull it on GPU. @@ -419,7 +598,7 @@ impl MeshUniform { material_bind_group_slot: MaterialBindGroupSlot, maybe_lightmap: Option<(LightmapSlotIndex, Rect)>, current_skin_index: Option, - previous_skin_index: Option, + tag: Option, ) -> Self { let (local_from_world_transpose_a, local_from_world_transpose_b) = mesh_transforms.world_from_local.inverse_transpose_3x3(); @@ -437,9 +616,10 @@ impl MeshUniform { flags: mesh_transforms.flags, first_vertex_index, current_skin_index: current_skin_index.unwrap_or(u32::MAX), - previous_skin_index: previous_skin_index.unwrap_or(u32::MAX), material_and_lightmap_bind_group_slot: u32::from(material_bind_group_slot) | ((lightmap_bind_group_slot as u32) << 16), + tag: tag.unwrap_or(0), + pad: 0, } } } @@ -568,6 +748,11 @@ pub struct RenderMeshInstanceShared { pub material_bindings_index: MaterialBindingId, /// Various flags. pub flags: RenderMeshInstanceFlags, + /// Index of the slab that the lightmap resides in, if a lightmap is + /// present. + pub lightmap_slab_index: Option, + /// User supplied tag to identify this mesh instance. + pub tag: u32, } /// Information that is gathered during the parallel portion of mesh extraction @@ -643,10 +828,18 @@ pub enum RenderMeshInstanceGpuQueue { #[derive(Resource, Default, Deref, DerefMut)] pub struct RenderMeshInstanceGpuQueues(Parallel); +/// Holds a list of meshes that couldn't be extracted this frame because their +/// materials weren't prepared yet. +/// +/// On subsequent frames, we try to reextract those meshes. +#[derive(Resource, Default, Deref, DerefMut)] +pub struct MeshesToReextractNextFrame(MainEntityHashSet); + impl RenderMeshInstanceShared { fn from_components( previous_transform: Option<&PreviousGlobalTransform>, mesh: &Mesh3d, + tag: Option<&MeshTag>, not_shadow_caster: bool, no_automatic_batching: bool, ) -> Self { @@ -666,6 +859,8 @@ impl RenderMeshInstanceShared { flags: mesh_instance_flags, // This gets filled in later, during `RenderMeshGpuBuilder::update`. material_bindings_index: default(), + lightmap_slab_index: None, + tag: tag.map_or(0, |i| **i), } } @@ -788,6 +983,7 @@ impl RenderMeshInstancesCpu { .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, translation: render_mesh_instance.transforms.world_from_local.translation, + current_uniform_index: InputUniformIndex::default(), }) } @@ -811,6 +1007,9 @@ impl RenderMeshInstancesGpu { .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, translation: render_mesh_instance.translation, + current_uniform_index: InputUniformIndex( + render_mesh_instance.current_uniform_index.into(), + ), }) } @@ -933,8 +1132,10 @@ impl RenderMeshInstanceGpuBuilder { mesh_material_ids: &RenderMeshMaterialIds, render_material_bindings: &RenderMaterialBindings, render_lightmaps: &RenderLightmaps, - skin_indices: &SkinIndices, - ) -> u32 { + skin_uniforms: &SkinUniforms, + timestamp: FrameCount, + meshes_to_reextract_next_frame: &mut MeshesToReextractNextFrame, + ) -> Option { let (first_vertex_index, vertex_count) = match mesh_allocator.mesh_vertex_slice(&self.shared.mesh_asset_id) { Some(mesh_vertex_slice) => ( @@ -952,28 +1153,34 @@ impl RenderMeshInstanceGpuBuilder { ), None => (false, 0, 0), }; - - let current_skin_index = match skin_indices.current.get(&entity) { - Some(skin_indices) => skin_indices.index(), - None => u32::MAX, - }; - let previous_skin_index = match skin_indices.prev.get(&entity) { - Some(skin_indices) => skin_indices.index(), + let current_skin_index = match skin_uniforms.skin_byte_offset(entity) { + Some(skin_index) => skin_index.index(), None => u32::MAX, }; - // Look up the material index. + // Look up the material index. If we couldn't fetch the material index, + // then the material hasn't been prepared yet, perhaps because it hasn't + // 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 = render_material_bindings - .get(&mesh_material) - .cloned() - .unwrap_or_default(); + 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; + } + }; self.shared.material_bindings_index = mesh_material_binding_id; let lightmap_slot = match render_lightmaps.render_lightmaps.get(&entity) { Some(render_lightmap) => u16::from(*render_lightmap.slot_index), None => u16::MAX, }; + let lightmap_slab_index = render_lightmaps + .render_lightmaps + .get(&entity) + .map(|lightmap| lightmap.slab_index); + self.shared.lightmap_slab_index = lightmap_slab_index; // Create the mesh input uniform. let mut mesh_input_uniform = MeshInputUniform { @@ -981,6 +1188,7 @@ impl RenderMeshInstanceGpuBuilder { lightmap_uv_rect: self.lightmap_uv_rect, flags: self.mesh_flags.bits(), previous_input_index: u32::MAX, + timestamp: timestamp.0, first_vertex_index, first_index_index, index_count: if mesh_is_indexed { @@ -989,12 +1197,11 @@ impl RenderMeshInstanceGpuBuilder { vertex_count }, current_skin_index, - previous_skin_index, material_and_lightmap_bind_group_slot: u32::from( self.shared.material_bindings_index.slot, ) | ((lightmap_slot as u32) << 16), - pad_a: 0, - pad_b: 0, + tag: self.shared.tag, + pad: 0, }; // Did the last frame contain this entity as well? @@ -1039,7 +1246,7 @@ impl RenderMeshInstanceGpuBuilder { } } - current_uniform_index + Some(current_uniform_index) } } @@ -1106,6 +1313,9 @@ pub struct RenderMeshQueueData<'a> { pub shared: &'a RenderMeshInstanceShared, /// The translation of the mesh instance. pub translation: Vec3, + /// The index of the [`MeshInputUniform`] in the GPU buffer for this mesh + /// instance. + pub current_uniform_index: InputUniformIndex, } /// A [`SystemSet`] that encompasses both [`extract_meshes_for_cpu_building`] @@ -1129,6 +1339,7 @@ pub fn extract_meshes_for_cpu_building( &GlobalTransform, Option<&PreviousGlobalTransform>, &Mesh3d, + Option<&MeshTag>, Has, Has, Has, @@ -1147,6 +1358,7 @@ pub fn extract_meshes_for_cpu_building( transform, previous_transform, mesh, + tag, no_frustum_culling, not_shadow_receiver, transmitted_receiver, @@ -1174,6 +1386,7 @@ pub fn extract_meshes_for_cpu_building( let shared = RenderMeshInstanceShared::from_components( previous_transform, mesh, + tag, not_shadow_caster, no_automatic_batching, ); @@ -1213,6 +1426,24 @@ pub fn extract_meshes_for_cpu_building( } } +/// All the data that we need from a mesh in the main world. +type GpuMeshExtractionQuery = ( + Entity, + Read, + Read, + Option>, + Option>, + Option>, + Read, + Option>, + Has, + Has, + Has, + Has, + Has, + Has, +); + /// Extracts meshes from the main world into the render world and queues /// [`MeshInputUniform`]s to be uploaded to the GPU. /// @@ -1227,21 +1458,7 @@ pub fn extract_meshes_for_gpu_building( mut render_mesh_instance_queues: ResMut, changed_meshes_query: Extract< Query< - ( - Entity, - &ViewVisibility, - &GlobalTransform, - Option<&PreviousGlobalTransform>, - Option<&Lightmap>, - Option<&Aabb>, - &Mesh3d, - Has, - Has, - Has, - Has, - Has, - Has, - ), + GpuMeshExtractionQuery, Or<( Changed, Changed, @@ -1255,13 +1472,16 @@ pub fn extract_meshes_for_gpu_building( Changed, Changed, Changed, + Changed, )>, >, >, + all_meshes_query: Extract>, mut removed_visibilities_query: Extract>, mut removed_global_transforms_query: Extract>, mut removed_meshes_query: Extract>, gpu_culling_query: Extract, Without)>>, + meshes_to_reextract_next_frame: ResMut, ) { let any_gpu_culling = !gpu_culling_query.is_empty(); @@ -1283,80 +1503,37 @@ pub fn extract_meshes_for_gpu_building( // construct the `MeshInputUniform` for them. changed_meshes_query.par_iter().for_each_init( || render_mesh_instance_queues.borrow_local_mut(), - |queue, - ( - entity, - view_visibility, - transform, - previous_transform, - lightmap, - aabb, - mesh, - no_frustum_culling, - not_shadow_receiver, - transmitted_receiver, - not_shadow_caster, - no_automatic_batching, - visibility_range, - )| { - if !view_visibility.get() { - queue.remove(entity.into(), any_gpu_culling); - return; - } - - let mut lod_index = None; - if visibility_range { - lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); - } - - let mesh_flags = MeshFlags::from_components( - transform, - lod_index, - no_frustum_culling, - not_shadow_receiver, - transmitted_receiver, - ); - - let shared = RenderMeshInstanceShared::from_components( - previous_transform, - mesh, - not_shadow_caster, - no_automatic_batching, - ); - - let lightmap_uv_rect = pack_lightmap_uv_rect(lightmap.map(|lightmap| lightmap.uv_rect)); - - let gpu_mesh_culling_data = any_gpu_culling.then(|| MeshCullingData::new(aabb)); - - let previous_input_index = if shared - .flags - .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM) - { - render_mesh_instances - .get(&MainEntity::from(entity)) - .map(|render_mesh_instance| render_mesh_instance.current_uniform_index) - } else { - None - }; - - let gpu_mesh_instance_builder = RenderMeshInstanceGpuBuilder { - shared, - world_from_local: (&transform.affine()).into(), - lightmap_uv_rect, - mesh_flags, - previous_input_index, - }; - - queue.push( - entity.into(), - gpu_mesh_instance_builder, - gpu_mesh_culling_data, + |queue, query_row| { + extract_mesh_for_gpu_building( + query_row, + &render_visibility_ranges, + render_mesh_instances, + queue, + any_gpu_culling, ); }, ); - // Also record info about each mesh that became invisible. + // Process materials that `collect_meshes_for_gpu_building` marked as + // needing to be reextracted. This will happen when we extracted a mesh on + // some previous frame, but its material hadn't been prepared yet, perhaps + // because the material hadn't yet been loaded. We reextract such materials + // on subsequent frames so that `collect_meshes_for_gpu_building` will check + // to see if their materials have been prepared. let mut queue = render_mesh_instance_queues.borrow_local_mut(); + for &mesh_entity in &**meshes_to_reextract_next_frame { + if let Ok(query_row) = all_meshes_query.get(*mesh_entity) { + extract_mesh_for_gpu_building( + query_row, + &render_visibility_ranges, + render_mesh_instances, + &mut queue, + any_gpu_culling, + ); + } + } + + // Also record info about each mesh that became invisible. for entity in removed_visibilities_query .read() .chain(removed_global_transforms_query.read()) @@ -1365,12 +1542,93 @@ pub fn extract_meshes_for_gpu_building( // 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) { - queue.remove(entity.into(), any_gpu_culling); + let entity = MainEntity::from(entity); + if !changed_meshes_query.contains(*entity) + && !meshes_to_reextract_next_frame.contains(&entity) + { + queue.remove(entity, any_gpu_culling); } } } +fn extract_mesh_for_gpu_building( + ( + entity, + view_visibility, + transform, + previous_transform, + lightmap, + aabb, + mesh, + tag, + no_frustum_culling, + not_shadow_receiver, + transmitted_receiver, + not_shadow_caster, + no_automatic_batching, + visibility_range, + ): ::Item<'_>, + render_visibility_ranges: &RenderVisibilityRanges, + render_mesh_instances: &RenderMeshInstancesGpu, + queue: &mut RenderMeshInstanceGpuQueue, + any_gpu_culling: bool, +) { + if !view_visibility.get() { + queue.remove(entity.into(), any_gpu_culling); + return; + } + + let mut lod_index = None; + if visibility_range { + lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); + } + + let mesh_flags = MeshFlags::from_components( + transform, + lod_index, + no_frustum_culling, + not_shadow_receiver, + transmitted_receiver, + ); + + let shared = RenderMeshInstanceShared::from_components( + previous_transform, + mesh, + tag, + not_shadow_caster, + no_automatic_batching, + ); + + let lightmap_uv_rect = pack_lightmap_uv_rect(lightmap.map(|lightmap| lightmap.uv_rect)); + + let gpu_mesh_culling_data = any_gpu_culling.then(|| MeshCullingData::new(aabb)); + + let previous_input_index = if shared + .flags + .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM) + { + render_mesh_instances + .get(&MainEntity::from(entity)) + .map(|render_mesh_instance| render_mesh_instance.current_uniform_index) + } else { + None + }; + + let gpu_mesh_instance_builder = RenderMeshInstanceGpuBuilder { + shared, + world_from_local: (&transform.affine()).into(), + lightmap_uv_rect, + mesh_flags, + previous_input_index, + }; + + queue.push( + entity.into(), + gpu_mesh_instance_builder, + gpu_mesh_culling_data, + ); +} + /// A system that sets the [`RenderMeshInstanceFlags`] for each mesh based on /// whether the previous frame had skins and/or morph targets. /// @@ -1387,10 +1645,10 @@ pub fn extract_meshes_for_gpu_building( /// loop. fn set_mesh_motion_vector_flags( mut render_mesh_instances: ResMut, - skin_indices: Res, + skin_uniforms: Res, morph_indices: Res, ) { - for &entity in skin_indices.prev.keys() { + for &entity in skin_uniforms.all_skins() { render_mesh_instances .insert_mesh_instance_flags(entity, RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN); } @@ -1413,7 +1671,9 @@ pub fn collect_meshes_for_gpu_building( mesh_material_ids: Res, render_material_bindings: Res, render_lightmaps: Res, - skin_indices: Res, + skin_uniforms: Res, + frame_count: Res, + mut meshes_to_reextract_next_frame: ResMut, ) { let RenderMeshInstances::GpuBuilding(ref mut render_mesh_instances) = render_mesh_instances.into_inner() @@ -1421,6 +1681,9 @@ pub fn collect_meshes_for_gpu_building( return; }; + // We're going to rebuild `meshes_to_reextract_next_frame`. + meshes_to_reextract_next_frame.clear(); + // Collect render mesh instances. Build up the uniform buffer. let gpu_preprocessing::BatchedInstanceBuffers { ref mut current_input_buffer, @@ -1452,7 +1715,9 @@ pub fn collect_meshes_for_gpu_building( &mesh_material_ids, &render_material_bindings, &render_lightmaps, - &skin_indices, + &skin_uniforms, + *frame_count, + &mut meshes_to_reextract_next_frame, ); } @@ -1470,7 +1735,7 @@ pub fn collect_meshes_for_gpu_building( ref mut removed, } => { for (entity, mesh_instance_builder, mesh_culling_builder) in changed.drain(..) { - let instance_data_index = mesh_instance_builder.update( + let Some(instance_data_index) = mesh_instance_builder.update( entity, &mut *render_mesh_instances, current_input_buffer, @@ -1479,8 +1744,12 @@ pub fn collect_meshes_for_gpu_building( &mesh_material_ids, &render_material_bindings, &render_lightmaps, - &skin_indices, - ); + &skin_uniforms, + *frame_count, + &mut meshes_to_reextract_next_frame, + ) else { + continue; + }; mesh_culling_builder .update(&mut mesh_culling_data_buffer, instance_data_index as usize); } @@ -1565,8 +1834,8 @@ impl FromWorld for MeshPipeline { let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( texture.as_image_copy(), - &image.data, - ImageDataLayout { + image.data.as_ref().expect("Image was created without data"), + TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(image.width() * format_size as u32), rows_per_image: None, @@ -1629,7 +1898,7 @@ impl GetBatchData for MeshPipeline { SRes, SRes>, SRes, - SRes, + SRes, ); // The material bind group ID, the mesh ID, and the lightmap ID, // respectively. @@ -1642,7 +1911,9 @@ impl GetBatchData for MeshPipeline { type BufferData = MeshUniform; fn get_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem, + (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< + Self::Param, + >, (_entity, main_entity): (Entity, MainEntity), ) -> Option<(Self::BufferData, Option)> { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1660,9 +1931,7 @@ impl GetBatchData for MeshPipeline { }; let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); - let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index); - let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index); - + let current_skin_index = skin_uniforms.skin_index(main_entity); let material_bind_group_index = mesh_instance.material_bindings_index; Some(( @@ -1672,7 +1941,7 @@ impl GetBatchData for MeshPipeline { material_bind_group_index.slot, maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), current_skin_index, - previous_skin_index, + Some(mesh_instance.tag), ), mesh_instance.should_batch().then_some(( material_bind_group_index.group, @@ -1713,7 +1982,9 @@ impl GetFullBatchData for MeshPipeline { } fn get_binned_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem, + (mesh_instances, lightmaps, _, mesh_allocator, skin_uniforms): &SystemParamItem< + Self::Param, + >, main_entity: MainEntity, ) -> Option { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1730,8 +2001,7 @@ impl GetFullBatchData for MeshPipeline { }; let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); - let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index); - let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index); + let current_skin_index = skin_uniforms.skin_index(main_entity); Some(MeshUniform::new( &mesh_instance.transforms, @@ -1739,7 +2009,7 @@ impl GetFullBatchData for MeshPipeline { mesh_instance.material_bindings_index.slot, maybe_lightmap.map(|lightmap| (lightmap.slot_index, lightmap.uv_rect)), current_skin_index, - previous_skin_index, + Some(mesh_instance.tag), )) } @@ -1762,29 +2032,28 @@ impl GetFullBatchData for MeshPipeline { } fn write_batch_indirect_parameters_metadata( - mesh_index: u32, indexed: bool, base_output_index: u32, batch_set_index: Option, - indirect_parameters_buffer: &mut IndirectParametersBuffers, + phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, indirect_parameters_offset: u32, ) { - let indirect_parameters = IndirectParametersMetadata { - mesh_index, + let indirect_parameters = IndirectParametersCpuMetadata { base_output_index, batch_set_index: match batch_set_index { Some(batch_set_index) => u32::from(batch_set_index), None => !0, }, - early_instance_count: 0, - late_instance_count: 0, }; if indexed { - indirect_parameters_buffer.set_indexed(indirect_parameters_offset, indirect_parameters); + phase_indirect_parameters_buffers + .indexed + .set(indirect_parameters_offset, indirect_parameters); } else { - indirect_parameters_buffer - .set_non_indexed(indirect_parameters_offset, indirect_parameters); + phase_indirect_parameters_buffers + .non_indexed + .set(indirect_parameters_offset, indirect_parameters); } } } @@ -2377,9 +2646,12 @@ impl SpecializedMeshPipeline for MeshPipeline { } } -/// Bind groups for meshes currently loaded. -#[derive(Resource, Default)] -pub struct MeshBindGroups { +/// The bind groups for meshes currently loaded. +/// +/// If GPU mesh preprocessing isn't in use, these are global to the scene. If +/// GPU mesh preprocessing is in use, these are specific to a single phase. +#[derive(Default)] +pub struct MeshPhaseBindGroups { model_only: Option, skinned: Option, morph_targets: HashMap, MeshBindGroupPair>, @@ -2391,7 +2663,18 @@ pub struct MeshBindGroupPair { no_motion_vectors: BindGroup, } -impl MeshBindGroups { +/// All bind groups for meshes currently loaded. +#[derive(Resource)] +pub enum MeshBindGroups { + /// The bind groups for the meshes for the entire scene, if GPU mesh + /// preprocessing isn't in use. + CpuPreprocessing(MeshPhaseBindGroups), + /// A mapping from the type ID of a phase (e.g. [`Opaque3d`]) to the mesh + /// bind groups for that phase. + GpuPreprocessing(TypeIdMap), +} + +impl MeshPhaseBindGroups { pub fn reset(&mut self) { self.model_only = None; self.skinned = None; @@ -2433,9 +2716,10 @@ impl MeshBindGroupPair { } } -pub fn prepare_mesh_bind_group( +/// Creates the per-mesh bind groups for each type of mesh and each phase. +pub fn prepare_mesh_bind_groups( + mut commands: Commands, meshes: Res>, - mut groups: ResMut, mesh_pipeline: Res, render_device: Res, cpu_batched_instance_buffer: Option< @@ -2448,36 +2732,88 @@ pub fn prepare_mesh_bind_group( weights_uniform: Res, mut render_lightmaps: ResMut, ) { - groups.reset(); + // CPU mesh preprocessing path. + if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer { + if let Some(instance_data_binding) = cpu_batched_instance_buffer + .into_inner() + .instance_data_binding() + { + // In this path, we only have a single set of bind groups for all phases. + let cpu_preprocessing_mesh_bind_groups = prepare_mesh_bind_groups_for_phase( + instance_data_binding, + &meshes, + &mesh_pipeline, + &render_device, + &skins_uniform, + &weights_uniform, + &mut render_lightmaps, + ); + + commands.insert_resource(MeshBindGroups::CpuPreprocessing( + cpu_preprocessing_mesh_bind_groups, + )); + return; + } + } + + // GPU mesh preprocessing path. + if let Some(gpu_batched_instance_buffers) = gpu_batched_instance_buffers { + let mut gpu_preprocessing_mesh_bind_groups = TypeIdMap::default(); + + // Loop over each phase. + for (phase_type_id, batched_phase_instance_buffers) in + &gpu_batched_instance_buffers.phase_instance_buffers + { + let Some(instance_data_binding) = + batched_phase_instance_buffers.instance_data_binding() + else { + continue; + }; + + let mesh_phase_bind_groups = prepare_mesh_bind_groups_for_phase( + instance_data_binding, + &meshes, + &mesh_pipeline, + &render_device, + &skins_uniform, + &weights_uniform, + &mut render_lightmaps, + ); + + gpu_preprocessing_mesh_bind_groups.insert(*phase_type_id, mesh_phase_bind_groups); + } + + commands.insert_resource(MeshBindGroups::GpuPreprocessing( + gpu_preprocessing_mesh_bind_groups, + )); + } +} +/// Creates the per-mesh bind groups for each type of mesh, for a single phase. +fn prepare_mesh_bind_groups_for_phase( + model: BindingResource, + meshes: &RenderAssets, + mesh_pipeline: &MeshPipeline, + render_device: &RenderDevice, + skins_uniform: &SkinUniforms, + weights_uniform: &MorphUniforms, + render_lightmaps: &mut RenderLightmaps, +) -> MeshPhaseBindGroups { let layouts = &mesh_pipeline.mesh_layouts; - let model = if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer { - cpu_batched_instance_buffer - .into_inner() - .instance_data_binding() - } else if let Some(gpu_batched_instance_buffers) = gpu_batched_instance_buffers { - gpu_batched_instance_buffers - .into_inner() - .instance_data_binding() - } else { - return; + // TODO: Reuse allocations. + let mut groups = MeshPhaseBindGroups { + model_only: Some(layouts.model_only(render_device, &model)), + ..default() }; - let Some(model) = model else { return }; - - groups.model_only = Some(layouts.model_only(&render_device, &model)); // Create the skinned mesh bind group with the current and previous buffers - // (the latter being for motion vector computation). If there's no previous - // buffer, just use the current one as the shader will ignore it. - let skin = skins_uniform.current_buffer.buffer(); - if let Some(skin) = skin { - let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin); - groups.skinned = Some(MeshBindGroupPair { - motion_vectors: layouts.skinned_motion(&render_device, &model, skin, prev_skin), - no_motion_vectors: layouts.skinned(&render_device, &model, skin), - }); - } + // (the latter being for motion vector computation). + let (skin, prev_skin) = (&skins_uniform.current_buffer, &skins_uniform.prev_buffer); + groups.skinned = Some(MeshBindGroupPair { + motion_vectors: layouts.skinned_motion(render_device, &model, skin, prev_skin), + no_motion_vectors: layouts.skinned(render_device, &model, skin), + }); // Create the morphed bind groups just like we did for the skinned bind // group. @@ -2485,43 +2821,37 @@ pub fn prepare_mesh_bind_group( let prev_weights = weights_uniform.prev_buffer.buffer().unwrap_or(weights); for (id, gpu_mesh) in meshes.iter() { if let Some(targets) = gpu_mesh.morph_targets.as_ref() { - let bind_group_pair = match skin.filter(|_| is_skinned(&gpu_mesh.layout)) { - Some(skin) => { - let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin); - MeshBindGroupPair { - motion_vectors: layouts.morphed_skinned_motion( - &render_device, - &model, - skin, - weights, - targets, - prev_skin, - prev_weights, - ), - no_motion_vectors: layouts.morphed_skinned( - &render_device, - &model, - skin, - weights, - targets, - ), - } - } - None => MeshBindGroupPair { - motion_vectors: layouts.morphed_motion( - &render_device, + let bind_group_pair = if is_skinned(&gpu_mesh.layout) { + let prev_skin = &skins_uniform.prev_buffer; + MeshBindGroupPair { + motion_vectors: layouts.morphed_skinned_motion( + render_device, &model, + skin, weights, targets, + prev_skin, prev_weights, ), - no_motion_vectors: layouts.morphed( - &render_device, + no_motion_vectors: layouts.morphed_skinned( + render_device, &model, + skin, weights, targets, ), - }, + } + } else { + MeshBindGroupPair { + motion_vectors: layouts.morphed_motion( + render_device, + &model, + weights, + targets, + prev_weights, + ), + no_motion_vectors: layouts.morphed(render_device, &model, weights, targets), + } }; groups.morph_targets.insert(id, bind_group_pair); } @@ -2533,9 +2863,11 @@ pub fn prepare_mesh_bind_group( for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter_mut().enumerate() { groups.lightmaps.insert( LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()), - layouts.lightmapped(&render_device, &model, lightmap_slab, bindless_supported), + layouts.lightmapped(render_device, &model, lightmap_slab, bindless_supported), ); } + + groups } pub struct SetMeshViewBindGroup; @@ -2593,7 +2925,7 @@ impl RenderCommand

for SetMeshBindGroup { SRes, SRes, SRes, - SRes, + SRes, SRes, SRes, ); @@ -2609,7 +2941,7 @@ impl RenderCommand

for SetMeshBindGroup { render_device, bind_groups, mesh_instances, - skin_indices, + skin_uniforms, morph_indices, lightmaps, ): SystemParamItem<'w, '_, Self::Param>, @@ -2617,7 +2949,7 @@ impl RenderCommand

for SetMeshBindGroup { ) -> RenderCommandResult { let bind_groups = bind_groups.into_inner(); let mesh_instances = mesh_instances.into_inner(); - let skin_indices = skin_indices.into_inner(); + let skin_uniforms = skin_uniforms.into_inner(); let morph_indices = morph_indices.into_inner(); let entity = &item.main_entity(); @@ -2626,12 +2958,11 @@ impl RenderCommand

for SetMeshBindGroup { return RenderCommandResult::Success; }; - let current_skin_index = skin_indices.current.get(entity); - let prev_skin_index = skin_indices.prev.get(entity); + let current_skin_byte_offset = skin_uniforms.skin_byte_offset(*entity); let current_morph_index = morph_indices.current.get(entity); let prev_morph_index = morph_indices.prev.get(entity); - let is_skinned = current_skin_index.is_some(); + let is_skinned = current_skin_byte_offset.is_some(); let is_morphed = current_morph_index.is_some(); let lightmap_slab_index = lightmaps @@ -2639,7 +2970,20 @@ impl RenderCommand

for SetMeshBindGroup { .get(entity) .map(|render_lightmap| render_lightmap.slab_index); - let Some(bind_group) = bind_groups.get( + let Some(mesh_phase_bind_groups) = (match *bind_groups { + MeshBindGroups::CpuPreprocessing(ref mesh_phase_bind_groups) => { + Some(mesh_phase_bind_groups) + } + MeshBindGroups::GpuPreprocessing(ref mesh_phase_bind_groups) => { + mesh_phase_bind_groups.get(&TypeId::of::

()) + } + }) else { + // This is harmless if e.g. we're rendering the `Shadow` phase and + // there weren't any shadows. + return RenderCommandResult::Success; + }; + + let Some(bind_group) = mesh_phase_bind_groups.get( mesh_asset_id, lightmap_slab_index, is_skinned, @@ -2659,7 +3003,7 @@ impl RenderCommand

for SetMeshBindGroup { dynamic_offsets[offset_count] = dynamic_offset; offset_count += 1; } - if let Some(current_skin_index) = current_skin_index { + if let Some(current_skin_index) = current_skin_byte_offset { if skin::skins_use_uniform_buffers(&render_device) { dynamic_offsets[offset_count] = current_skin_index.byte_offset; offset_count += 1; @@ -2672,16 +3016,12 @@ impl RenderCommand

for SetMeshBindGroup { // Attach motion vectors if needed. if has_motion_vector_prepass { - // Attach the previous skin index for motion vector computation. If - // there isn't one, just use zero as the shader will ignore it. - if current_skin_index.is_some() && skin::skins_use_uniform_buffers(&render_device) { - match prev_skin_index { - Some(prev_skin_index) => { - dynamic_offsets[offset_count] = prev_skin_index.byte_offset; - } - None => dynamic_offsets[offset_count] = 0, + // Attach the previous skin index for motion vector computation. + if skin::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; } - offset_count += 1; } // Attach the previous morph index for motion vector computation. If @@ -2791,9 +3131,20 @@ impl RenderCommand

for DrawMesh { // Look up the indirect parameters buffer, as well as // the buffer we're going to use for // `multi_draw_indexed_indirect_count` (if available). + let Some(phase_indirect_parameters_buffers) = + indirect_parameters_buffer.get(&TypeId::of::

()) + else { + warn!( + "Not rendering mesh because indexed indirect parameters buffer \ + wasn't present for this phase", + ); + return RenderCommandResult::Skip; + }; let (Some(indirect_parameters_buffer), Some(batch_sets_buffer)) = ( - indirect_parameters_buffer.indexed_data_buffer(), - indirect_parameters_buffer.indexed_batch_sets_buffer(), + phase_indirect_parameters_buffers.indexed.data_buffer(), + phase_indirect_parameters_buffers + .indexed + .batch_sets_buffer(), ) else { warn!( "Not rendering mesh because indexed indirect parameters buffer \ @@ -2848,9 +3199,20 @@ impl RenderCommand

for DrawMesh { // Look up the indirect parameters buffer, as well as the // buffer we're going to use for // `multi_draw_indirect_count` (if available). + let Some(phase_indirect_parameters_buffers) = + indirect_parameters_buffer.get(&TypeId::of::

()) + else { + warn!( + "Not rendering mesh because non-indexed indirect parameters buffer \ + wasn't present for this phase", + ); + return RenderCommandResult::Skip; + }; let (Some(indirect_parameters_buffer), Some(batch_sets_buffer)) = ( - indirect_parameters_buffer.non_indexed_data_buffer(), - indirect_parameters_buffer.non_indexed_batch_sets_buffer(), + phase_indirect_parameters_buffers.non_indexed.data_buffer(), + phase_indirect_parameters_buffers + .non_indexed + .batch_sets_buffer(), ) else { warn!( "Not rendering mesh because non-indexed indirect parameters buffer \ diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 23857bc6aa12d..c912cb734dd67 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -132,3 +132,10 @@ fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4 u32 { + return mesh[instance_index].tag; +} +#endif diff --git a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl index 315dd13d3fdd6..543b328aaafd5 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl @@ -14,7 +14,9 @@ // are known as *early mesh preprocessing* and *late mesh preprocessing* // respectively. -#import bevy_pbr::mesh_preprocess_types::{IndirectParametersMetadata, MeshInput} +#import bevy_pbr::mesh_preprocess_types::{ + IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, MeshInput +} #import bevy_pbr::mesh_types::{Mesh, MESH_FLAGS_NO_FRUSTUM_CULLING_BIT} #import bevy_pbr::mesh_view_bindings::view #import bevy_pbr::occlusion_culling @@ -43,11 +45,10 @@ struct PreprocessWorkItem { // The index of the `MeshInput` in the `current_input` buffer that we read // from. input_index: u32, - // The index of the `Mesh` in `output` that we write to. - output_index: u32, - // The index of the `IndirectParameters` in `indirect_parameters` that we - // write to. - indirect_parameters_index: u32, + // In direct mode, the index of the `Mesh` in `output` that we write to. In + // indirect mode, the index of the `IndirectParameters` in + // `indirect_parameters` that we write to. + output_or_indirect_parameters_index: u32, } // The parameters for the indirect compute dispatch for the late mesh @@ -91,15 +92,18 @@ struct PushConstants { #ifdef INDIRECT // The array of indirect parameters for drawcalls. -@group(0) @binding(7) var indirect_parameters_metadata: - array; +@group(0) @binding(7) var indirect_parameters_cpu_metadata: + array; + +@group(0) @binding(8) var indirect_parameters_gpu_metadata: + array; #endif #ifdef FRUSTUM_CULLING // Data needed to cull the meshes. // // At the moment, this consists only of AABBs. -@group(0) @binding(8) var mesh_culling_data: array; +@group(0) @binding(9) var mesh_culling_data: array; #endif // FRUSTUM_CULLING #ifdef OCCLUSION_CULLING @@ -132,9 +136,9 @@ fn view_frustum_intersects_obb( let relative_radius = dot( abs( vec3( - dot(plane_normal, world_from_local[0]), - dot(plane_normal, world_from_local[1]), - dot(plane_normal, world_from_local[2]), + dot(plane_normal.xyz, world_from_local[0].xyz), + dot(plane_normal.xyz, world_from_local[1].xyz), + dot(plane_normal.xyz, world_from_local[2].xyz), ) ), aabb_half_extents @@ -171,8 +175,21 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { // Unpack the work item. let input_index = work_items[instance_index].input_index; - let output_index = work_items[instance_index].output_index; - let indirect_parameters_index = work_items[instance_index].indirect_parameters_index; +#ifdef INDIRECT + let indirect_parameters_index = work_items[instance_index].output_or_indirect_parameters_index; + + // If we're the first mesh instance in this batch, write the index of our + // `MeshInput` into the appropriate slot so that the indirect parameters + // building shader can access it. +#ifndef LATE_PHASE + if (instance_index == 0u || work_items[instance_index - 1].output_or_indirect_parameters_index != indirect_parameters_index) { + indirect_parameters_gpu_metadata[indirect_parameters_index].mesh_index = input_index; + } +#endif // LATE_PHASE + +#else // INDIRECT + let mesh_output_index = work_items[instance_index].output_or_indirect_parameters_index; +#endif // INDIRECT // Unpack the input matrix. let world_from_local_affine_transpose = current_input[input_index].world_from_local; @@ -192,14 +209,22 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { } #endif - // Look up the previous model matrix. + // See whether the `MeshInputUniform` was updated on this frame. If it + // wasn't, then we know the transforms of this mesh must be identical to + // those on the previous frame, and therefore we don't need to access the + // `previous_input_index` (in fact, we can't; that index are only valid for + // one frame and will be invalid). + let timestamp = current_input[input_index].timestamp; + let mesh_changed_this_frame = timestamp == view.frame_count; + + // Look up the previous model matrix, if it could have been. let previous_input_index = current_input[input_index].previous_input_index; var previous_world_from_local_affine_transpose: mat3x4; - if (previous_input_index == 0xffffffff) { - previous_world_from_local_affine_transpose = world_from_local_affine_transpose; - } else { + if (mesh_changed_this_frame && previous_input_index != 0xffffffffu) { previous_world_from_local_affine_transpose = previous_input[previous_input_index].world_from_local; + } else { + previous_world_from_local_affine_transpose = world_from_local_affine_transpose; } let previous_world_from_local = maths::affine3_to_square(previous_world_from_local_affine_transpose); @@ -289,8 +314,7 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { // Enqueue a work item for the late prepass phase. late_preprocess_work_items[output_work_item_index].input_index = input_index; - late_preprocess_work_items[output_work_item_index].output_index = output_index; - late_preprocess_work_items[output_work_item_index].indirect_parameters_index = + late_preprocess_work_items[output_work_item_index].output_or_indirect_parameters_index = indirect_parameters_index; #endif // EARLY_PHASE // This mesh is culled. Skip it. @@ -314,22 +338,23 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { // parameters. Otherwise, this index was directly supplied to us. #ifdef INDIRECT #ifdef LATE_PHASE - let batch_output_index = - atomicLoad(&indirect_parameters_metadata[indirect_parameters_index].early_instance_count) + - atomicAdd(&indirect_parameters_metadata[indirect_parameters_index].late_instance_count, 1u); + let batch_output_index = atomicLoad( + &indirect_parameters_gpu_metadata[indirect_parameters_index].early_instance_count + ) + atomicAdd( + &indirect_parameters_gpu_metadata[indirect_parameters_index].late_instance_count, + 1u + ); #else // LATE_PHASE let batch_output_index = atomicAdd( - &indirect_parameters_metadata[indirect_parameters_index].early_instance_count, + &indirect_parameters_gpu_metadata[indirect_parameters_index].early_instance_count, 1u ); #endif // LATE_PHASE let mesh_output_index = - indirect_parameters_metadata[indirect_parameters_index].base_output_index + + indirect_parameters_cpu_metadata[indirect_parameters_index].base_output_index + batch_output_index; -#else // INDIRECT - let mesh_output_index = output_index; #endif // INDIRECT // Write the output. @@ -342,7 +367,7 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { output[mesh_output_index].lightmap_uv_rect = current_input[input_index].lightmap_uv_rect; output[mesh_output_index].first_vertex_index = current_input[input_index].first_vertex_index; output[mesh_output_index].current_skin_index = current_input[input_index].current_skin_index; - output[mesh_output_index].previous_skin_index = current_input[input_index].previous_skin_index; output[mesh_output_index].material_and_lightmap_bind_group_slot = current_input[input_index].material_and_lightmap_bind_group_slot; + output[mesh_output_index].tag = current_input[input_index].tag; } diff --git a/crates/bevy_pbr/src/render/mesh_types.wgsl b/crates/bevy_pbr/src/render/mesh_types.wgsl index f0258770c6da0..502b91b427d7f 100644 --- a/crates/bevy_pbr/src/render/mesh_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_types.wgsl @@ -18,10 +18,12 @@ struct Mesh { // The index of the mesh's first vertex in the vertex buffer. first_vertex_index: u32, current_skin_index: u32, - previous_skin_index: u32, // Low 16 bits: index of the material inside the bind group data. // High 16 bits: index of the lightmap in the binding array. material_and_lightmap_bind_group_slot: u32, + // User supplied index to identify the mesh instance + tag: u32, + pad: u32, }; #ifdef SKINNED diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index bd759a1296ae0..796ae096c2e26 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -216,7 +216,7 @@ fn layout_entries( ( 2, #[cfg(all( - not(feature = "ios_simulator"), + not(target_abi = "sim"), any( not(feature = "webgl"), not(target_arch = "wasm32"), @@ -225,7 +225,7 @@ fn layout_entries( ))] texture_cube_array(TextureSampleType::Depth), #[cfg(any( - feature = "ios_simulator", + target_abi = "sim", all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")) ))] texture_cube(TextureSampleType::Depth), diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 8e26e869a1c96..4b0a20ebecff5 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -13,4 +13,4 @@ pub use light::*; pub use mesh::*; pub use mesh_bindings::MeshLayouts; pub use mesh_view_bindings::*; -pub use skin::{extract_skins, prepare_skins, SkinIndices, SkinUniforms, MAX_JOINTS}; +pub use skin::{extract_skins, prepare_skins, SkinUniforms, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/parallax_mapping.wgsl b/crates/bevy_pbr/src/render/parallax_mapping.wgsl index 780b5c290a416..9005734da5cc1 100644 --- a/crates/bevy_pbr/src/render/parallax_mapping.wgsl +++ b/crates/bevy_pbr/src/render/parallax_mapping.wgsl @@ -1,10 +1,16 @@ #define_import_path bevy_pbr::parallax_mapping +#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d} + #import bevy_pbr::{ pbr_bindings::{depth_map_texture, depth_map_sampler}, mesh_bindings::mesh } +#ifdef BINDLESS +#import bevy_pbr::pbr_bindings::material_indices +#endif // BINDLESS + fn sample_depth_map(uv: vec2, material_bind_group_slot: u32) -> f32 { // We use `textureSampleLevel` over `textureSample` because the wgpu DX12 // backend (Fxc) panics when using "gradient instructions" inside a loop. @@ -18,8 +24,8 @@ fn sample_depth_map(uv: vec2, material_bind_group_slot: u32) -> f32 { // See https://stackoverflow.com/questions/56581141/direct3d11-gradient-instruction-used-in-a-loop-with-varying-iteration-forcing return textureSampleLevel( #ifdef BINDLESS - depth_map_texture[material_bind_group_slot], - depth_map_sampler[material_bind_group_slot], + bindless_textures_2d[material_indices[material_bind_group_slot].depth_map_texture], + bindless_samplers_filtering[material_indices[material_bind_group_slot].depth_map_sampler], #else // BINDLESS depth_map_texture, depth_map_sampler, diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index d6514acfa9b55..1e77cba98528f 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -3,20 +3,53 @@ #import bevy_pbr::pbr_types::StandardMaterial #ifdef BINDLESS -@group(2) @binding(0) var material: binding_array; -@group(2) @binding(1) var base_color_texture: binding_array, 16>; -@group(2) @binding(2) var base_color_sampler: binding_array; -@group(2) @binding(3) var emissive_texture: binding_array, 16>; -@group(2) @binding(4) var emissive_sampler: binding_array; -@group(2) @binding(5) var metallic_roughness_texture: binding_array, 16>; -@group(2) @binding(6) var metallic_roughness_sampler: binding_array; -@group(2) @binding(7) var occlusion_texture: binding_array, 16>; -@group(2) @binding(8) var occlusion_sampler: binding_array; -@group(2) @binding(9) var normal_map_texture: binding_array, 16>; -@group(2) @binding(10) var normal_map_sampler: binding_array; -@group(2) @binding(11) var depth_map_texture: binding_array, 16>; -@group(2) @binding(12) var depth_map_sampler: binding_array; +struct StandardMaterialBindings { + material: u32, // 0 + base_color_texture: u32, // 1 + base_color_sampler: u32, // 2 + emissive_texture: u32, // 3 + emissive_sampler: u32, // 4 + metallic_roughness_texture: u32, // 5 + metallic_roughness_sampler: u32, // 6 + occlusion_texture: u32, // 7 + occlusion_sampler: u32, // 8 + normal_map_texture: u32, // 9 + 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; +@group(2) @binding(10) var material_array: binding_array; + #else // BINDLESS + @group(2) @binding(0) var material: StandardMaterial; @group(2) @binding(1) var base_color_texture: texture_2d; @group(2) @binding(2) var base_color_sampler: sampler; @@ -30,64 +63,35 @@ @group(2) @binding(10) var normal_map_sampler: sampler; @group(2) @binding(11) var depth_map_texture: texture_2d; @group(2) @binding(12) var depth_map_sampler: sampler; -#endif // BINDLESS #ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED -#ifdef BINDLESS -@group(2) @binding(13) var anisotropy_texture: binding_array, 16>; -@group(2) @binding(14) var anisotropy_sampler: binding_array; -#else // BINDLESS @group(2) @binding(13) var anisotropy_texture: texture_2d; @group(2) @binding(14) var anisotropy_sampler: sampler; -#endif // BINDLESS #endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED -#ifdef BINDLESS -@group(2) @binding(15) var specular_transmission_texture: binding_array, 16>; -@group(2) @binding(16) var specular_transmission_sampler: binding_array; -@group(2) @binding(17) var thickness_texture: binding_array, 16>; -@group(2) @binding(18) var thickness_sampler: binding_array; -@group(2) @binding(19) var diffuse_transmission_texture: binding_array, 16>; -@group(2) @binding(20) var diffuse_transmission_sampler: binding_array; -#else // BINDLESS @group(2) @binding(15) var specular_transmission_texture: texture_2d; @group(2) @binding(16) var specular_transmission_sampler: sampler; @group(2) @binding(17) var thickness_texture: texture_2d; @group(2) @binding(18) var thickness_sampler: sampler; @group(2) @binding(19) var diffuse_transmission_texture: texture_2d; @group(2) @binding(20) var diffuse_transmission_sampler: sampler; -#endif // BINDLESS #endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED #ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED -#ifdef BINDLESS -@group(2) @binding(21) var clearcoat_texture: binding_array, 16>; -@group(2) @binding(22) var clearcoat_sampler: binding_array; -@group(2) @binding(23) var clearcoat_roughness_texture: binding_array, 16>; -@group(2) @binding(24) var clearcoat_roughness_sampler: binding_array; -@group(2) @binding(25) var clearcoat_normal_texture: binding_array, 16>; -@group(2) @binding(26) var clearcoat_normal_sampler: binding_array; -#else // BINDLESS @group(2) @binding(21) var clearcoat_texture: texture_2d; @group(2) @binding(22) var clearcoat_sampler: sampler; @group(2) @binding(23) var clearcoat_roughness_texture: texture_2d; @group(2) @binding(24) var clearcoat_roughness_sampler: sampler; @group(2) @binding(25) var clearcoat_normal_texture: texture_2d; @group(2) @binding(26) var clearcoat_normal_sampler: sampler; -#endif // BINDLESS #endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED #ifdef PBR_SPECULAR_TEXTURES_SUPPORTED -#ifdef BINDLESS -@group(2) @binding(27) var specular_texture: binding_array, 16>; -@group(2) @binding(28) var specular_sampler: binding_array; -@group(2) @binding(29) var specular_tint_texture: binding_array, 16>; -@group(2) @binding(30) var specular_tint_sampler: binding_array; -#else @group(2) @binding(27) var specular_texture: texture_2d; @group(2) @binding(28) var specular_sampler: sampler; @group(2) @binding(29) var specular_tint_texture: texture_2d; @group(2) @binding(30) var specular_tint_sampler: sampler; -#endif // BINDLESS #endif // PBR_SPECULAR_TEXTURES_SUPPORTED + +#endif // BINDLESS diff --git a/crates/bevy_pbr/src/render/pbr_fragment.wgsl b/crates/bevy_pbr/src/render/pbr_fragment.wgsl index ac68e9f0aa0c3..779546f8bd67d 100644 --- a/crates/bevy_pbr/src/render/pbr_fragment.wgsl +++ b/crates/bevy_pbr/src/render/pbr_fragment.wgsl @@ -1,5 +1,7 @@ #define_import_path bevy_pbr::pbr_fragment +#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d} + #import bevy_pbr::{ pbr_functions, pbr_functions::SampleBias, @@ -26,6 +28,10 @@ #import bevy_pbr::forward_io::VertexOutput #endif +#ifdef BINDLESS +#import bevy_pbr::pbr_bindings::material_indices +#endif // BINDLESS + // prepare a basic PbrInput from the vertex stage output, mesh binding and view binding fn pbr_input_from_vertex_output( in: VertexOutput, @@ -70,17 +76,17 @@ fn pbr_input_from_standard_material( in: VertexOutput, is_front: bool, ) -> pbr_types::PbrInput { -#ifdef BINDLESS #ifdef MESHLET_MESH_MATERIAL_PASS let slot = in.material_bind_group_slot; #else // MESHLET_MESH_MATERIAL_PASS let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; #endif // MESHLET_MESH_MATERIAL_PASS - let flags = pbr_bindings::material[slot].flags; - let base_color = pbr_bindings::material[slot].base_color; - let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id; +#ifdef BINDLESS + let flags = pbr_bindings::material_array[material_indices[slot].material].flags; + let base_color = pbr_bindings::material_array[material_indices[slot].material].base_color; + let deferred_lighting_pass_id = + pbr_bindings::material_array[material_indices[slot].material].deferred_lighting_pass_id; #else // BINDLESS - let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu; let flags = pbr_bindings::material.flags; let base_color = pbr_bindings::material.base_color; let deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; @@ -109,7 +115,7 @@ fn pbr_input_from_standard_material( #ifdef VERTEX_UVS #ifdef BINDLESS - let uv_transform = pbr_bindings::material[slot].uv_transform; + let uv_transform = pbr_bindings::material_array[material_indices[slot].material].uv_transform; #else // BINDLESS let uv_transform = pbr_bindings::material.uv_transform; #endif // BINDLESS @@ -138,9 +144,9 @@ fn pbr_input_from_standard_material( // TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass uv = parallaxed_uv( #ifdef BINDLESS - pbr_bindings::material[slot].parallax_depth_scale, - pbr_bindings::material[slot].max_parallax_layer_count, - pbr_bindings::material[slot].max_relief_mapping_search_steps, + pbr_bindings::material_array[material_indices[slot].material].parallax_depth_scale, + pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count, + pbr_bindings::material_array[material_indices[slot].material].max_relief_mapping_search_steps, #else // BINDLESS pbr_bindings::material.parallax_depth_scale, pbr_bindings::material.max_parallax_layer_count, @@ -159,9 +165,9 @@ fn pbr_input_from_standard_material( // TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass uv_b = parallaxed_uv( #ifdef BINDLESS - pbr_bindings::material[slot].parallax_depth_scale, - pbr_bindings::material[slot].max_parallax_layer_count, - pbr_bindings::material[slot].max_relief_mapping_search_steps, + pbr_bindings::material_array[material_indices[slot].material].parallax_depth_scale, + pbr_bindings::material_array[material_indices[slot].material].max_parallax_layer_count, + pbr_bindings::material_array[material_indices[slot].material].max_relief_mapping_search_steps, #else // BINDLESS pbr_bindings::material.parallax_depth_scale, pbr_bindings::material.max_parallax_layer_count, @@ -188,8 +194,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::base_color_texture[slot], - pbr_bindings::base_color_sampler[slot], + bindless_textures_2d[material_indices[slot].base_color_texture], + bindless_samplers_filtering[material_indices[slot].base_color_sampler], #else // BINDLESS pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, @@ -215,7 +221,7 @@ fn pbr_input_from_standard_material( if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE { #ifdef BINDLESS - let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; + let alpha_cutoff = pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff; #else // BINDLESS let alpha_cutoff = pbr_bindings::material.alpha_cutoff; #endif // BINDLESS @@ -233,10 +239,13 @@ fn pbr_input_from_standard_material( // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { #ifdef BINDLESS - pbr_input.material.ior = pbr_bindings::material[slot].ior; - pbr_input.material.attenuation_color = pbr_bindings::material[slot].attenuation_color; - pbr_input.material.attenuation_distance = pbr_bindings::material[slot].attenuation_distance; - pbr_input.material.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; + pbr_input.material.ior = pbr_bindings::material_array[material_indices[slot].material].ior; + pbr_input.material.attenuation_color = + pbr_bindings::material_array[material_indices[slot].material].attenuation_color; + pbr_input.material.attenuation_distance = + pbr_bindings::material_array[material_indices[slot].material].attenuation_distance; + pbr_input.material.alpha_cutoff = + pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff; #else // BINDLESS pbr_input.material.ior = pbr_bindings::material.ior; pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color; @@ -246,7 +255,8 @@ fn pbr_input_from_standard_material( // reflectance #ifdef BINDLESS - pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance; + pbr_input.material.reflectance = + pbr_bindings::material_array[material_indices[slot].material].reflectance; #else // BINDLESS pbr_input.material.reflectance = pbr_bindings::material.reflectance; #endif // BINDLESS @@ -263,8 +273,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::specular_texture[slot], - pbr_bindings::specular_sampler[slot], + bindless_textures_2d[material_indices[slot].specular_texture], + bindless_samplers_filtering[material_indices[slot].specular_sampler], #else // BINDLESS pbr_bindings::specular_texture, pbr_bindings::specular_sampler, @@ -295,8 +305,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::specular_tint_texture[slot], - pbr_bindings::specular_tint_sampler[slot], + bindless_textures_2d[material_indices[slot].specular_tint_texture], + bindless_samplers_filtering[material_indices[slot].specular_tint_sampler], #else // BINDLESS pbr_bindings::specular_tint_texture, pbr_bindings::specular_tint_sampler, @@ -321,7 +331,7 @@ fn pbr_input_from_standard_material( // emissive #ifdef BINDLESS - var emissive: vec4 = pbr_bindings::material[slot].emissive; + var emissive: vec4 = pbr_bindings::material_array[material_indices[slot].material].emissive; #else // BINDLESS var emissive: vec4 = pbr_bindings::material.emissive; #endif // BINDLESS @@ -335,8 +345,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::emissive_texture[slot], - pbr_bindings::emissive_sampler[slot], + bindless_textures_2d[material_indices[slot].emissive_texture], + bindless_samplers_filtering[material_indices[slot].emissive_sampler], #else // BINDLESS pbr_bindings::emissive_texture, pbr_bindings::emissive_sampler, @@ -360,8 +370,8 @@ fn pbr_input_from_standard_material( // metallic and perceptual roughness #ifdef BINDLESS - var metallic: f32 = pbr_bindings::material[slot].metallic; - var perceptual_roughness: f32 = pbr_bindings::material[slot].perceptual_roughness; + var metallic: f32 = pbr_bindings::material_array[material_indices[slot].material].metallic; + var perceptual_roughness: f32 = pbr_bindings::material_array[material_indices[slot].material].perceptual_roughness; #else // BINDLESS var metallic: f32 = pbr_bindings::material.metallic; var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness; @@ -377,8 +387,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::metallic_roughness_texture[slot], - pbr_bindings::metallic_roughness_sampler[slot], + bindless_textures_2d[material_indices[slot].metallic_roughness_texture], + bindless_samplers_filtering[material_indices[slot].metallic_roughness_sampler], #else // BINDLESS pbr_bindings::metallic_roughness_texture, pbr_bindings::metallic_roughness_sampler, @@ -405,7 +415,8 @@ fn pbr_input_from_standard_material( // Clearcoat factor #ifdef BINDLESS - pbr_input.material.clearcoat = pbr_bindings::material[slot].clearcoat; + pbr_input.material.clearcoat = + pbr_bindings::material_array[material_indices[slot].material].clearcoat; #else // BINDLESS pbr_input.material.clearcoat = pbr_bindings::material.clearcoat; #endif // BINDLESS @@ -420,8 +431,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::clearcoat_texture[slot], - pbr_bindings::clearcoat_sampler[slot], + bindless_textures_2d[material_indices[slot].clearcoat_texture], + bindless_samplers_filtering[material_indices[slot].clearcoat_sampler], #else // BINDLESS pbr_bindings::clearcoat_texture, pbr_bindings::clearcoat_sampler, @@ -445,7 +456,7 @@ fn pbr_input_from_standard_material( // Clearcoat roughness #ifdef BINDLESS pbr_input.material.clearcoat_perceptual_roughness = - pbr_bindings::material[slot].clearcoat_perceptual_roughness; + pbr_bindings::material_array[material_indices[slot].material].clearcoat_perceptual_roughness; #else // BINDLESS pbr_input.material.clearcoat_perceptual_roughness = pbr_bindings::material.clearcoat_perceptual_roughness; @@ -461,8 +472,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::clearcoat_roughness_texture[slot], - pbr_bindings::clearcoat_roughness_sampler[slot], + bindless_textures_2d[material_indices[slot].clearcoat_roughness_texture], + bindless_samplers_filtering[material_indices[slot].clearcoat_roughness_sampler], #else // BINDLESS pbr_bindings::clearcoat_roughness_texture, pbr_bindings::clearcoat_roughness_sampler, @@ -484,7 +495,7 @@ fn pbr_input_from_standard_material( #endif // VERTEX_UVS #ifdef BINDLESS - var specular_transmission: f32 = pbr_bindings::material[slot].specular_transmission; + var specular_transmission: f32 = pbr_bindings::material_array[slot].specular_transmission; #else // BINDLESS var specular_transmission: f32 = pbr_bindings::material.specular_transmission; #endif // BINDLESS @@ -499,8 +510,12 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::specular_transmission_texture[slot], - pbr_bindings::specular_transmission_sampler[slot], + bindless_textures_2d[ + material_indices[slot].specular_transmission_texture + ], + bindless_samplers_filtering[ + material_indices[slot].specular_transmission_sampler + ], #else // BINDLESS pbr_bindings::specular_transmission_texture, pbr_bindings::specular_transmission_sampler, @@ -523,7 +538,7 @@ fn pbr_input_from_standard_material( pbr_input.material.specular_transmission = specular_transmission; #ifdef BINDLESS - var thickness: f32 = pbr_bindings::material[slot].thickness; + var thickness: f32 = pbr_bindings::material_array[material_indices[slot].material].thickness; #else // BINDLESS var thickness: f32 = pbr_bindings::material.thickness; #endif // BINDLESS @@ -538,8 +553,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::thickness_texture[slot], - pbr_bindings::thickness_sampler[slot], + bindless_textures_2d[material_indices[slot].thickness_texture], + bindless_samplers_filtering[material_indices[slot].thickness_sampler], #else // BINDLESS pbr_bindings::thickness_texture, pbr_bindings::thickness_sampler, @@ -569,7 +584,8 @@ fn pbr_input_from_standard_material( pbr_input.material.thickness = thickness; #ifdef BINDLESS - var diffuse_transmission = pbr_bindings::material[slot].diffuse_transmission; + var diffuse_transmission = + pbr_bindings::material_array[material_indices[slot].material].diffuse_transmission; #else // BINDLESS var diffuse_transmission = pbr_bindings::material.diffuse_transmission; #endif // BINDLESS @@ -584,8 +600,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::diffuse_transmission_texture[slot], - pbr_bindings::diffuse_transmission_sampler[slot], + bindless_textures_2d[material_indices[slot].diffuse_transmission_texture], + bindless_samplers_filtering[material_indices[slot].diffuse_transmission_sampler], #else // BINDLESS pbr_bindings::diffuse_transmission_texture, pbr_bindings::diffuse_transmission_sampler, @@ -618,8 +634,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::occlusion_texture[slot], - pbr_bindings::occlusion_sampler[slot], + bindless_textures_2d[material_indices[slot].occlusion_texture], + bindless_samplers_filtering[material_indices[slot].occlusion_sampler], #else // BINDLESS pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, @@ -669,8 +685,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::normal_map_texture[slot], - pbr_bindings::normal_map_sampler[slot], + bindless_textures_2d[material_indices[slot].normal_map_texture], + bindless_samplers_filtering[material_indices[slot].normal_map_sampler], #else // BINDLESS pbr_bindings::normal_map_texture, pbr_bindings::normal_map_sampler, @@ -707,8 +723,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::clearcoat_normal_texture[slot], - pbr_bindings::clearcoat_normal_sampler[slot], + bindless_textures_2d[material_indices[slot].clearcoat_normal_texture], + bindless_samplers_filtering[material_indices[slot].clearcoat_normal_sampler], #else // BINDLESS pbr_bindings::clearcoat_normal_texture, pbr_bindings::clearcoat_normal_sampler, @@ -750,8 +766,10 @@ fn pbr_input_from_standard_material( #ifdef STANDARD_MATERIAL_ANISOTROPY #ifdef BINDLESS - var anisotropy_strength = pbr_bindings::material[slot].anisotropy_strength; - var anisotropy_direction = pbr_bindings::material[slot].anisotropy_rotation; + var anisotropy_strength = + pbr_bindings::material_array[material_indices[slot].material].anisotropy_strength; + var anisotropy_direction = + pbr_bindings::material_array[material_indices[slot].material].anisotropy_rotation; #else // BINDLESS var anisotropy_strength = pbr_bindings::material.anisotropy_strength; var anisotropy_direction = pbr_bindings::material.anisotropy_rotation; @@ -766,8 +784,8 @@ fn pbr_input_from_standard_material( textureSampleBias( #endif // MESHLET_MESH_MATERIAL_PASS #ifdef BINDLESS - pbr_bindings::anisotropy_texture[slot], - pbr_bindings::anisotropy_sampler[slot], + bindless_textures_2d[material_indices[slot].anisotropy_texture], + bindless_samplers_filtering[material_indices[slot].anisotropy_sampler], #else // BINDLESS pbr_bindings::anisotropy_texture, pbr_bindings::anisotropy_sampler, @@ -810,7 +828,8 @@ fn pbr_input_from_standard_material( #ifdef LIGHTMAP #ifdef BINDLESS - let lightmap_exposure = pbr_bindings::material[slot].lightmap_exposure; + let lightmap_exposure = + pbr_bindings::material_array[material_indices[slot].material].lightmap_exposure; #else // BINDLESS let lightmap_exposure = pbr_bindings::material.lightmap_exposure; #endif // BINDLESS diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 44890b3a65613..e9b4e1f1a844d 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -241,7 +241,7 @@ fn bend_normal_for_anisotropy(lighting_input: ptr = pbr_bindings::material[slot].base_color; + var output_color: vec4 = pbr_bindings::material_array[material_indices[slot].material].base_color; #else // BINDLESS var output_color: vec4 = pbr_bindings::material.base_color; #endif // BINDLESS @@ -31,8 +37,8 @@ fn prepass_alpha_discard(in: VertexOutput) { #endif // STANDARD_MATERIAL_BASE_COLOR_UV_B #ifdef BINDLESS - let uv_transform = pbr_bindings::material[slot].uv_transform; - let flags = pbr_bindings::material[slot].flags; + 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; @@ -42,8 +48,8 @@ fn prepass_alpha_discard(in: VertexOutput) { if (flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { output_color = output_color * textureSampleBias( #ifdef BINDLESS - pbr_bindings::base_color_texture[slot], - pbr_bindings::base_color_sampler[slot], + bindless_textures_2d[material_indices[slot].base_color_texture], + bindless_samplers_filtering[material_indices[slot].base_color_sampler], #else // BINDLESS pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, @@ -57,7 +63,7 @@ fn prepass_alpha_discard(in: VertexOutput) { let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { #ifdef BINDLESS - let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; + let alpha_cutoff = pbr_bindings::material_array[material_indices[slot].material].alpha_cutoff; #else // BINDLESS let alpha_cutoff = pbr_bindings::material.alpha_cutoff; #endif // BINDLESS diff --git a/crates/bevy_pbr/src/render/shadow_sampling.wgsl b/crates/bevy_pbr/src/render/shadow_sampling.wgsl index 22f2e28310bd0..c7f7253a6357d 100644 --- a/crates/bevy_pbr/src/render/shadow_sampling.wgsl +++ b/crates/bevy_pbr/src/render/shadow_sampling.wgsl @@ -47,7 +47,7 @@ fn search_for_blockers_in_shadow_map_hardware( view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_linear_sampler, light_local, - 0.0, + 0u, ); #else // NO_ARRAY_TEXTURES_SUPPORT let sampled_depth = textureSampleLevel( @@ -55,7 +55,7 @@ fn search_for_blockers_in_shadow_map_hardware( view_bindings::directional_shadow_textures_linear_sampler, light_local, array_index, - 0.0, + 0u, ); #endif // NO_ARRAY_TEXTURES_SUPPORT return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth); diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index c248821ccafd3..bec846a038e6a 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,19 +1,24 @@ use core::mem::{self, size_of}; use std::sync::OnceLock; -use bevy_asset::Assets; +use bevy_asset::{prelude::AssetChanged, Assets}; use bevy_ecs::prelude::*; use bevy_math::Mat4; -use bevy_render::sync_world::MainEntityHashMap; +use bevy_platform_support::collections::hash_map::Entry; +use bevy_render::render_resource::{Buffer, BufferDescriptor}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; use bevy_render::{ batching::NoAutomaticBatching, mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - render_resource::{BufferUsages, RawBufferVec}, + render_resource::BufferUsages, renderer::{RenderDevice, RenderQueue}, view::ViewVisibility, Extract, }; use bevy_transform::prelude::GlobalTransform; +use offset_allocator::{Allocation, Allocator}; +use smallvec::SmallVec; +use tracing::error; /// Maximum number of joints supported for skinned meshes. /// @@ -24,18 +29,40 @@ use bevy_transform::prelude::GlobalTransform; /// of the GPU at runtime, which would mean not using consts anymore. pub const MAX_JOINTS: usize = 256; +/// The total number of joints we support. +/// +/// This is 256 GiB worth of joint matrices, which we will never hit under any +/// reasonable circumstances. +const MAX_TOTAL_JOINTS: u32 = 1024 * 1024 * 1024; + +/// The number of joints that we allocate at a time. +/// +/// Some hardware requires that uniforms be allocated on 256-byte boundaries, so +/// we need to allocate 4 64-byte matrices at a time to satisfy alignment +/// requirements. +const JOINTS_PER_ALLOCATION_UNIT: u32 = (256 / size_of::()) as u32; + +/// The maximum ratio of the number of entities whose transforms changed to the +/// total number of joints before we re-extract all joints. +/// +/// We use this as a heuristic to decide whether it's worth switching over to +/// fine-grained detection to determine which skins need extraction. If the +/// number of changed entities is over this threshold, we skip change detection +/// and simply re-extract the transforms of all joints. +const JOINT_EXTRACTION_THRESHOLD_FACTOR: f64 = 0.25; + /// The location of the first joint matrix in the skin uniform buffer. -#[derive(Component)] -pub struct SkinIndex { +#[derive(Clone, Copy)] +pub struct SkinByteOffset { /// The byte offset of the first joint matrix. pub byte_offset: u32, } -impl SkinIndex { +impl SkinByteOffset { /// Index to be in address space based on the size of a skin uniform. - const fn new(start: usize) -> Self { - SkinIndex { - byte_offset: (start * size_of::()) as u32, + const fn from_index(index: usize) -> Self { + SkinByteOffset { + byte_offset: (index * size_of::()) as u32, } } @@ -47,22 +74,6 @@ impl SkinIndex { } } -/// Maps each skinned mesh to the applicable offset within the [`SkinUniforms`] -/// buffer. -/// -/// We store both the current frame's joint matrices and the previous frame's -/// joint matrices for the purposes of motion vector calculation. -#[derive(Default, Resource)] -pub struct SkinIndices { - /// Maps each skinned mesh to the applicable offset within - /// [`SkinUniforms::current_buffer`]. - pub current: MainEntityHashMap, - - /// Maps each skinned mesh to the applicable offset within - /// [`SkinUniforms::prev_buffer`]. - pub prev: MainEntityHashMap, -} - /// The GPU buffers containing joint matrices for all skinned meshes. /// /// This is double-buffered: we store the joint matrices of each mesh for the @@ -74,28 +85,109 @@ pub struct SkinIndices { /// Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] pub struct SkinUniforms { - /// Stores all the joint matrices for skinned meshes in the current frame. - pub current_buffer: RawBufferVec, - /// Stores all the joint matrices for skinned meshes in the previous frame. - pub prev_buffer: RawBufferVec, + /// The CPU-side buffer that stores the joint matrices for skinned meshes in + /// the current frame. + pub current_staging_buffer: Vec, + /// The GPU-side buffer that stores the joint matrices for skinned meshes in + /// the current frame. + pub current_buffer: Buffer, + /// The GPU-side buffer that stores the joint matrices for skinned meshes in + /// the previous frame. + pub prev_buffer: Buffer, + /// The offset allocator that manages the placement of the joints within the + /// [`Self::current_buffer`]. + allocator: Allocator, + /// Allocation information that we keep about each skin. + skin_uniform_info: MainEntityHashMap, + /// Maps each joint entity to the skins it's associated with. + /// + /// We use this in conjunction with change detection to only update the + /// skins that need updating each frame. + /// + /// Note that conceptually this is a hash map of sets, but we use a + /// [`SmallVec`] to avoid allocations for the vast majority of the cases in + /// which each bone belongs to exactly one skin. + joint_to_skins: MainEntityHashMap>, + /// The total number of joints in the scene. + /// + /// We use this as part of our heuristic to decide whether to use + /// fine-grained change detection. + total_joints: usize, } impl FromWorld for SkinUniforms { fn from_world(world: &mut World) -> Self { let device = world.resource::(); - let buffer_usages = if skins_use_uniform_buffers(device) { + let buffer_usages = (if skins_use_uniform_buffers(device) { BufferUsages::UNIFORM } else { BufferUsages::STORAGE - }; + }) | BufferUsages::COPY_DST; + + // Create the current and previous buffer with the minimum sizes. + // + // These will be swapped every frame. + let current_buffer = device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + size: MAX_JOINTS as u64 * size_of::() as u64, + usage: buffer_usages, + mapped_at_creation: false, + }); + let prev_buffer = device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + size: MAX_JOINTS as u64 * size_of::() as u64, + usage: buffer_usages, + mapped_at_creation: false, + }); Self { - current_buffer: RawBufferVec::new(buffer_usages), - prev_buffer: RawBufferVec::new(buffer_usages), + current_staging_buffer: vec![], + current_buffer, + prev_buffer, + allocator: Allocator::new(MAX_TOTAL_JOINTS), + skin_uniform_info: MainEntityHashMap::default(), + joint_to_skins: MainEntityHashMap::default(), + total_joints: 0, } } } +impl SkinUniforms { + /// Returns the current offset in joints of the skin in the buffer. + pub fn skin_index(&self, skin: MainEntity) -> Option { + self.skin_uniform_info + .get(&skin) + .map(SkinUniformInfo::offset) + } + + /// Returns the current offset in bytes of the skin in the buffer. + pub fn skin_byte_offset(&self, skin: MainEntity) -> Option { + self.skin_uniform_info.get(&skin).map(|skin_uniform_info| { + SkinByteOffset::from_index(skin_uniform_info.offset() as usize) + }) + } + + /// Returns an iterator over all skins in the scene. + pub fn all_skins(&self) -> impl Iterator { + self.skin_uniform_info.keys() + } +} + +/// Allocation information about each skin. +struct SkinUniformInfo { + /// The allocation of the joints within the [`SkinUniforms::current_buffer`]. + allocation: Allocation, + /// The entities that comprise the joints. + joints: Vec, +} + +impl SkinUniformInfo { + /// The offset in joints within the [`SkinUniforms::current_staging_buffer`]. + fn offset(&self) -> u32 { + self.allocation.offset * JOINTS_PER_ALLOCATION_UNIT + } +} + /// Returns true if skinning must use uniforms (and dynamic offsets) because /// storage buffers aren't supported on the current platform. pub fn skins_use_uniform_buffers(render_device: &RenderDevice) -> bool { @@ -104,20 +196,54 @@ pub fn skins_use_uniform_buffers(render_device: &RenderDevice) -> bool { .get_or_init(|| render_device.limits().max_storage_buffers_per_shader_stage == 0) } +/// Uploads the buffers containing the joints to the GPU. pub fn prepare_skins( render_device: Res, render_queue: Res, - mut uniform: ResMut, + uniform: ResMut, ) { - if uniform.current_buffer.is_empty() { + let uniform = uniform.into_inner(); + + if uniform.current_staging_buffer.is_empty() { return; } - let len = uniform.current_buffer.len(); - uniform.current_buffer.reserve(len, &render_device); - uniform - .current_buffer - .write_buffer(&render_device, &render_queue); + // 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` + // 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) + * size_of::() as u64; + if uniform.current_buffer.size() < needed_size { + let mut new_size = uniform.current_buffer.size(); + while new_size < needed_size { + // 1.5× growth factor. + new_size += new_size / 2; + } + + // Create a new buffer. + let buffer_usages = if skins_use_uniform_buffers(&render_device) { + BufferUsages::UNIFORM + } else { + BufferUsages::STORAGE + } | BufferUsages::COPY_DST; + uniform.current_buffer = render_device.create_buffer(&BufferDescriptor { + label: Some("skin uniform buffer"), + usage: buffer_usages, + size: new_size, + mapped_at_creation: false, + }); + } + + // Write the data from `uniform.current_staging_buffer` into + // `uniform.current_buffer`. + render_queue.write_buffer( + &uniform.current_buffer, + 0, + bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]), + ); // We don't need to write `uniform.prev_buffer` because we already wrote it // last frame, and the data should still be on the GPU. @@ -150,71 +276,320 @@ pub fn prepare_skins( // which normally only support fixed size arrays. You just have to make sure // in the shader that you only read the values that are valid for that binding. pub fn extract_skins( - skin_indices: ResMut, - uniform: ResMut, - query: Extract>, - inverse_bindposes: Extract>>, + skin_uniforms: ResMut, + skinned_meshes: Extract>, + changed_skinned_meshes: Extract< + Query< + (Entity, &ViewVisibility, &SkinnedMesh), + Or<( + Changed, + Changed, + AssetChanged, + )>, + >, + >, + skinned_mesh_inverse_bindposes: Extract>>, + changed_transforms: Extract>>, joints: Extract>, - render_device: Res, + mut removed_visibilities_query: Extract>, + mut removed_skinned_meshes_query: Extract>, ) { - let skins_use_uniform_buffers = skins_use_uniform_buffers(&render_device); + let skin_uniforms = skin_uniforms.into_inner(); - // Borrow check workaround. - let (skin_indices, uniform) = (skin_indices.into_inner(), uniform.into_inner()); + // Find skins that have become visible or invisible on this frame. Allocate, + // reallocate, or free space for them as necessary. + add_or_delete_skins( + skin_uniforms, + &changed_skinned_meshes, + &skinned_mesh_inverse_bindposes, + &joints, + ); - // Swap buffers. We need to keep the previous frame's buffer around for the - // purposes of motion vector computation. - mem::swap(&mut skin_indices.current, &mut skin_indices.prev); - mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer); - skin_indices.current.clear(); - uniform.current_buffer.clear(); + // Extract the transforms for all joints from the scene, and write them into + // the staging buffer at the appropriate spot. + extract_joints( + skin_uniforms, + &skinned_meshes, + &changed_skinned_meshes, + &skinned_mesh_inverse_bindposes, + &changed_transforms, + &joints, + ); + + // Delete skins that became invisible. + for skinned_mesh_entity in removed_visibilities_query + .read() + .chain(removed_skinned_meshes_query.read()) + { + // Only remove a skin if we didn't pick it up in `add_or_delete_skins`. + // It's possible that a necessary component was removed and re-added in + // the same frame. + if !changed_skinned_meshes.contains(skinned_mesh_entity) { + remove_skin(skin_uniforms, skinned_mesh_entity.into()); + } + } +} - let mut last_start = 0; +/// Searches for all skins that have become visible or invisible this frame and +/// allocations for them as necessary. +fn add_or_delete_skins( + skin_uniforms: &mut SkinUniforms, + changed_skinned_meshes: &Query< + (Entity, &ViewVisibility, &SkinnedMesh), + Or<( + Changed, + Changed, + AssetChanged, + )>, + >, + skinned_mesh_inverse_bindposes: &Assets, + joints: &Query<&GlobalTransform>, +) { + // Find every skinned mesh that changed one of (1) visibility; (2) joint + // entities (part of `SkinnedMesh`); (3) the associated + // `SkinnedMeshInverseBindposes` asset. + for (skinned_mesh_entity, skinned_mesh_view_visibility, skinned_mesh) in changed_skinned_meshes + { + // Remove the skin if it existed last frame. + let skinned_mesh_entity = MainEntity::from(skinned_mesh_entity); + remove_skin(skin_uniforms, skinned_mesh_entity); - // PERF: This can be expensive, can we move this to prepare? - for (entity, view_visibility, skin) in &query { - if !view_visibility.get() { + // If the skin is invisible, we're done. + if !(*skinned_mesh_view_visibility).get() { continue; } - let buffer = &mut uniform.current_buffer; - let Some(inverse_bindposes) = inverse_bindposes.get(&skin.inverse_bindposes) else { + + // Initialize the skin. + add_skin( + skinned_mesh_entity, + skinned_mesh, + skin_uniforms, + skinned_mesh_inverse_bindposes, + joints, + ); + } +} + +/// Extracts the global transforms of all joints and updates the staging buffer +/// as necessary. +fn extract_joints( + skin_uniforms: &mut SkinUniforms, + skinned_meshes: &Query<(Entity, &SkinnedMesh)>, + changed_skinned_meshes: &Query< + (Entity, &ViewVisibility, &SkinnedMesh), + Or<( + Changed, + Changed, + AssetChanged, + )>, + >, + skinned_mesh_inverse_bindposes: &Assets, + changed_transforms: &Query<(Entity, &GlobalTransform), Changed>, + joints: &Query<&GlobalTransform>, +) { + // If the number of entities that changed transforms exceeds a certain + // fraction (currently 25%) of the total joints in the scene, then skip + // fine-grained change detection. + // + // Note that this is a crude heuristic, for performance reasons. It doesn't + // consider the ratio of modified *joints* to total joints, only the ratio + // of modified *entities* to total joints. Thus in the worst case we might + // end up re-extracting all skins even though none of the joints changed. + // But making the heuristic finer-grained would make it slower to evaluate, + // and we don't want to lose performance. + let threshold = + (skin_uniforms.total_joints as f64 * JOINT_EXTRACTION_THRESHOLD_FACTOR).floor() as usize; + + if changed_transforms.iter().nth(threshold).is_some() { + // Go ahead and re-extract all skins in the scene. + for (skin_entity, skin) in skinned_meshes { + extract_joints_for_skin( + skin_entity.into(), + skin, + skin_uniforms, + changed_skinned_meshes, + skinned_mesh_inverse_bindposes, + joints, + ); + } + return; + } + + // Use fine-grained change detection to figure out only the skins that need + // to have their joints re-extracted. + let dirty_skins: MainEntityHashSet = changed_transforms + .iter() + .flat_map(|(joint, _)| skin_uniforms.joint_to_skins.get(&MainEntity::from(joint))) + .flat_map(|skin_joint_mappings| skin_joint_mappings.iter()) + .copied() + .collect(); + + // Re-extract the joints for only those skins. + for skin_entity in dirty_skins { + let Ok((_, skin)) = skinned_meshes.get(*skin_entity) else { continue; }; - let start = buffer.len(); - - let target = start + skin.joints.len().min(MAX_JOINTS); - buffer.extend( - joints - .iter_many(&skin.joints) - .zip(inverse_bindposes.iter()) - .take(MAX_JOINTS) - .map(|(joint, bindpose)| joint.affine() * *bindpose), + extract_joints_for_skin( + skin_entity, + skin, + skin_uniforms, + changed_skinned_meshes, + skinned_mesh_inverse_bindposes, + joints, ); - // iter_many will skip any failed fetches. This will cause it to assign the wrong bones, - // so just bail by truncating to the start. - if buffer.len() != target { - buffer.truncate(start); + } +} + +/// Extracts all joints for a single skin and writes their transforms into the +/// CPU staging buffer. +fn extract_joints_for_skin( + skin_entity: MainEntity, + skin: &SkinnedMesh, + skin_uniforms: &mut SkinUniforms, + changed_skinned_meshes: &Query< + (Entity, &ViewVisibility, &SkinnedMesh), + Or<( + Changed, + Changed, + AssetChanged, + )>, + >, + skinned_mesh_inverse_bindposes: &Assets, + joints: &Query<&GlobalTransform>, +) { + // If we initialized the skin this frame, we already populated all + // the joints, so there's no need to populate them again. + if changed_skinned_meshes.contains(*skin_entity) { + return; + } + + // Fetch information about the skin. + let Some(skin_uniform_info) = skin_uniforms.skin_uniform_info.get(&skin_entity) else { + return; + }; + let Some(skinned_mesh_inverse_bindposes) = + skinned_mesh_inverse_bindposes.get(&skin.inverse_bindposes) + else { + return; + }; + + // Calculate and write in the new joint matrices. + for (joint_index, (&joint, skinned_mesh_inverse_bindpose)) in skin + .joints + .iter() + .zip(skinned_mesh_inverse_bindposes.iter()) + .enumerate() + { + let Ok(joint_transform) = joints.get(joint) else { continue; - } - last_start = last_start.max(start); + }; + + let joint_matrix = joint_transform.affine() * *skinned_mesh_inverse_bindpose; + skin_uniforms.current_staging_buffer[skin_uniform_info.offset() as usize + joint_index] = + joint_matrix; + } +} + +/// Allocates space for a new skin in the buffers, and populates its joints. +fn add_skin( + skinned_mesh_entity: MainEntity, + skinned_mesh: &SkinnedMesh, + skin_uniforms: &mut SkinUniforms, + skinned_mesh_inverse_bindposes: &Assets, + joints: &Query<&GlobalTransform>, +) { + // Allocate space for the joints. + let Some(allocation) = skin_uniforms.allocator.allocate( + skinned_mesh + .joints + .len() + .div_ceil(JOINTS_PER_ALLOCATION_UNIT as usize) as u32, + ) else { + error!( + "Out of space for skin: {:?}. Tried to allocate space for {:?} joints.", + skinned_mesh_entity, + skinned_mesh.joints.len() + ); + return; + }; + + // Store that allocation. + let skin_uniform_info = SkinUniformInfo { + allocation, + joints: skinned_mesh + .joints + .iter() + .map(|entity| MainEntity::from(*entity)) + .collect(), + }; + + let skinned_mesh_inverse_bindposes = + skinned_mesh_inverse_bindposes.get(&skinned_mesh.inverse_bindposes); - // Pad to 256 byte alignment if we're using a uniform buffer. - // There's no need to do this if we're using storage buffers, though. - if skins_use_uniform_buffers { - while buffer.len() % 4 != 0 { - buffer.push(Mat4::ZERO); + for (joint_index, &joint) in skinned_mesh.joints.iter().enumerate() { + // Calculate the initial joint matrix. + let skinned_mesh_inverse_bindpose = + skinned_mesh_inverse_bindposes.and_then(|skinned_mesh_inverse_bindposes| { + skinned_mesh_inverse_bindposes.get(joint_index) + }); + let joint_matrix = match (skinned_mesh_inverse_bindpose, joints.get(joint)) { + (Some(skinned_mesh_inverse_bindpose), Ok(transform)) => { + transform.affine() * *skinned_mesh_inverse_bindpose } + _ => Mat4::IDENTITY, + }; + + // Write in the new joint matrix, growing the staging buffer if + // necessary. + let buffer_index = skin_uniform_info.offset() as usize + joint_index; + if skin_uniforms.current_staging_buffer.len() < buffer_index + 1 { + skin_uniforms + .current_staging_buffer + .resize(buffer_index + 1, Mat4::IDENTITY); } + skin_uniforms.current_staging_buffer[buffer_index] = joint_matrix; - skin_indices - .current - .insert(entity.into(), SkinIndex::new(start)); + // Record the inverse mapping from the joint back to the skin. We use + // this in order to perform fine-grained joint extraction. + skin_uniforms + .joint_to_skins + .entry(MainEntity::from(joint)) + .or_default() + .push(skinned_mesh_entity); } - // Pad out the buffer to ensure that there's enough space for bindings - while uniform.current_buffer.len() - last_start < MAX_JOINTS { - uniform.current_buffer.push(Mat4::ZERO); + // Record the number of joints. + skin_uniforms.total_joints += skinned_mesh.joints.len(); + + skin_uniforms + .skin_uniform_info + .insert(skinned_mesh_entity, skin_uniform_info); +} + +/// Deallocates a skin and removes it from the [`SkinUniforms`]. +fn remove_skin(skin_uniforms: &mut SkinUniforms, skinned_mesh_entity: MainEntity) { + let Some(old_skin_uniform_info) = skin_uniforms.skin_uniform_info.remove(&skinned_mesh_entity) + else { + return; + }; + + // Free the allocation. + skin_uniforms + .allocator + .free(old_skin_uniform_info.allocation); + + // Remove the inverse mapping from each joint back to the skin. + for &joint in &old_skin_uniform_info.joints { + if let Entry::Occupied(mut entry) = skin_uniforms.joint_to_skins.entry(joint) { + entry.get_mut().retain(|skin| *skin != skinned_mesh_entity); + if entry.get_mut().is_empty() { + entry.remove(); + } + } } + + // Update the total number of joints. + skin_uniforms.total_joints -= old_skin_uniform_info.joints.len(); } // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 92e977aeb1b92..1762a73887c94 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -34,7 +34,7 @@ fn skin_model( + weights.z * joint_matrices.data[indexes.z] + weights.w * joint_matrices.data[indexes.w]; #else // SKINS_USE_UNIFORM_BUFFERS - let skin_index = mesh[instance_index].current_skin_index; + var skin_index = mesh[instance_index].current_skin_index; return weights.x * joint_matrices[skin_index + indexes.x] + weights.y * joint_matrices[skin_index + indexes.y] + weights.z * joint_matrices[skin_index + indexes.z] @@ -57,7 +57,7 @@ fn skin_prev_model( + weights.z * prev_joint_matrices.data[indexes.z] + weights.w * prev_joint_matrices.data[indexes.w]; #else // SKINS_USE_UNIFORM_BUFFERS - let skin_index = mesh[instance_index].previous_skin_index; + let skin_index = mesh[instance_index].current_skin_index; return weights.x * prev_joint_matrices[skin_index + indexes.x] + weights.y * prev_joint_matrices[skin_index + indexes.y] + weights.z * prev_joint_matrices[skin_index + indexes.z] diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index cdb069b2a1d93..4d97f52cff4ff 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, prelude::Camera3d, @@ -39,10 +39,13 @@ use bevy_utils::prelude::default; use core::mem; use tracing::{error, warn}; -const PREPROCESS_DEPTH_SHADER_HANDLE: Handle = Handle::weak_from_u128(102258915420479); -const SSAO_SHADER_HANDLE: Handle = Handle::weak_from_u128(253938746510568); -const SPATIAL_DENOISE_SHADER_HANDLE: Handle = Handle::weak_from_u128(466162052558226); -const SSAO_UTILS_SHADER_HANDLE: Handle = Handle::weak_from_u128(366465052568786); +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; diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 4d2847f986cab..15b783cef5d5f 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, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::{ graph::{Core3d, Node3d}, @@ -49,8 +49,8 @@ use crate::{ ViewLightsUniformOffset, }; -const SSR_SHADER_HANDLE: Handle = Handle::weak_from_u128(10438925299917978850); -const RAYMARCH_SHADER_HANDLE: Handle = Handle::weak_from_u128(8517409683450840946); +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. /// diff --git a/crates/bevy_pbr/src/ssr/raymarch.wgsl b/crates/bevy_pbr/src/ssr/raymarch.wgsl index 0731057287b8c..e149edfbbc239 100644 --- a/crates/bevy_pbr/src/ssr/raymarch.wgsl +++ b/crates/bevy_pbr/src/ssr/raymarch.wgsl @@ -242,9 +242,9 @@ fn depth_raymarch_distance_fn_evaluate( // * The shrink-wrap surface is no longer continuous, so it's possible for rays to miss it. let linear_depth = - 1.0 / textureSampleLevel(depth_prepass_texture, depth_linear_sampler, interp_uv, 0.0); + 1.0 / textureSampleLevel(depth_prepass_texture, depth_linear_sampler, interp_uv, 0u); let unfiltered_depth = - 1.0 / textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, interp_uv, 0.0); + 1.0 / textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, interp_uv, 0u); var max_depth: f32; var min_depth: f32; @@ -286,7 +286,7 @@ struct DepthRayMarchResult { /// Range: `0..=1` as a lerp factor over `ray_start_cs..=ray_end_cs`. hit_t: f32, - /// UV correspindong to `hit_t`. + /// UV corresponding to `hit_t`. hit_uv: vec2, /// The distance that the hit point penetrates into the hit surface. diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 753bf67d68625..292ad1c86ae1a 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::{AssetId, Handle}; +use bevy_asset::{weak_handle, AssetId, Handle}; use bevy_color::ColorToComponents as _; use bevy_core_pipeline::{ core_3d::Camera3d, @@ -78,21 +78,22 @@ bitflags! { } /// The volumetric fog shader. -pub const VOLUMETRIC_FOG_HANDLE: Handle = Handle::weak_from_u128(17400058287583986650); +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. /// /// This mesh is simply stretched to the size of the framebuffer, as when the /// camera is inside a fog volume it's essentially a full-screen effect. -pub const PLANE_MESH: Handle = Handle::weak_from_u128(435245126479971076); +pub const PLANE_MESH: Handle = weak_handle!("92523617-c708-4fd0-b42f-ceb4300c930b"); /// The cube mesh, which is used to render a fog volume that the camera is /// outside. /// /// Note that only the front faces of this cuboid will be rasterized in /// hardware. The back faces will be calculated in the shader via raytracing. -pub const CUBE_MESH: Handle = Handle::weak_from_u128(5023959819001661507); +pub const CUBE_MESH: Handle = weak_handle!("4a1dd661-2d91-4377-a17a-a914e21e277e"); /// The total number of bind group layouts. /// diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 0cc210b845b97..68862bbf711ed 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -1,6 +1,6 @@ use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d}; use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; +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}; @@ -11,7 +11,8 @@ use bevy_render::{ render_resource::*, }; -pub const WIREFRAME_SHADER_HANDLE: Handle = Handle::weak_from_u128(192598014480025766); +pub const WIREFRAME_SHADER_HANDLE: Handle = + weak_handle!("2646a633-f8e3-4380-87ae-b44d881abbce"); /// A [`Plugin`] that draws wireframes. /// diff --git a/crates/bevy_picking/LICENSE-APACHE b/crates/bevy_picking/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_picking/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_picking/LICENSE-MIT b/crates/bevy_picking/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_picking/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_picking/src/backend.rs b/crates/bevy_picking/src/backend.rs index 597b238620c7f..5c18d57211af7 100644 --- a/crates/bevy_picking/src/backend.rs +++ b/crates/bevy_picking/src/backend.rs @@ -105,7 +105,8 @@ pub struct HitData { /// distance from the pointer to the hit, measured from the near plane of the camera, to the /// point, in world space. pub depth: f32, - /// The position of the intersection in the world, if the data is available from the backend. + /// The position reported by the backend, if the data is available. Position data may be in any + /// space (e.g. World space, Screen space, Local space), specified by the backend providing it. pub position: Option, /// The normal vector of the hit test, if the data is available from the backend. pub normal: Option, diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index f5a4b8d67f412..70934604637db 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -40,6 +40,7 @@ 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; @@ -72,7 +73,7 @@ pub struct Pointer { pub event: E, } -/// A traversal query (eg it implements [`Traversal`]) intended for use with [`Pointer`] events. +/// A traversal query (i.e. it implements [`Traversal`]) intended for use with [`Pointer`] events. /// /// This will always traverse to the parent, if the entity being visited has one. Otherwise, it /// propagates to the pointer's window and stops there. @@ -143,7 +144,7 @@ impl Pointer { } } -/// Fires when a pointer is canceled, and it's current interaction state is dropped. +/// Fires when a pointer is canceled, and its current interaction state is dropped. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Cancel { /// Information about the picking intersection. @@ -285,6 +286,19 @@ pub struct DragEntry { pub latest_pos: Vec2, } +/// Fires while a pointer is scrolling over the `target` entity. +#[derive(Clone, PartialEq, Debug, Reflect)] +pub struct Scroll { + /// The mouse scroll unit. + pub unit: MouseScrollUnit, + /// The horizontal scroll value. + pub x: f32, + /// The vertical scroll value. + pub y: f32, + /// Information about the picking intersection. + pub hit: HitData, +} + /// An entry in the cache that drives the `pointer_events` system, storing additional data /// about pointer button presses. #[derive(Debug, Clone, Default)] @@ -346,6 +360,7 @@ pub struct PickingEventWriters<'w> { drag_leave_events: EventWriter<'w, Pointer>, drag_over_events: EventWriter<'w, Pointer>, drag_start_events: EventWriter<'w, Pointer>, + scroll_events: EventWriter<'w, Pointer>, move_events: EventWriter<'w, Pointer>, out_events: EventWriter<'w, Pointer>, over_events: EventWriter<'w, Pointer>, @@ -449,7 +464,7 @@ pub fn pointer_events( Out { hit: hit.clone() }, ); commands.trigger_targets(out_event.clone(), hovered_entity); - event_writers.out_events.send(out_event); + event_writers.out_events.write(out_event); // Possibly send DragLeave events for button in PointerButton::iter() { @@ -467,7 +482,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_leave_event.clone(), hovered_entity); - event_writers.drag_leave_events.send(drag_leave_event); + event_writers.drag_leave_events.write(drag_leave_event); } } } @@ -513,7 +528,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_enter_event.clone(), hovered_entity); - event_writers.drag_enter_events.send(drag_enter_event); + event_writers.drag_enter_events.write(drag_enter_event); } } @@ -525,7 +540,7 @@ pub fn pointer_events( Over { hit: hit.clone() }, ); commands.trigger_targets(over_event.clone(), hovered_entity); - event_writers.over_events.send(over_event); + event_writers.over_events.write(over_event); } } @@ -556,7 +571,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(pressed_event.clone(), hovered_entity); - event_writers.pressed_events.send(pressed_event); + event_writers.pressed_events.write(pressed_event); // Also insert the press into the state state .pressing @@ -585,7 +600,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(click_event.clone(), hovered_entity); - event_writers.click_events.send(click_event); + event_writers.click_events.write(click_event); } // Always send the Released event let released_event = Pointer::new( @@ -598,7 +613,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(released_event.clone(), hovered_entity); - event_writers.released_events.send(released_event); + event_writers.released_events.write(released_event); } // Then emit the drop events. @@ -616,7 +631,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_drop_event.clone(), *dragged_over); - event_writers.drag_drop_events.send(drag_drop_event); + event_writers.drag_drop_events.write(drag_drop_event); } // Emit DragEnd let drag_end_event = Pointer::new( @@ -629,7 +644,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_end_event.clone(), drag_target); - event_writers.drag_end_events.send(drag_end_event); + event_writers.drag_end_events.write(drag_end_event); // Emit DragLeave for (dragged_over, hit) in state.dragging_over.iter() { let drag_leave_event = Pointer::new( @@ -643,7 +658,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_leave_event.clone(), *dragged_over); - event_writers.drag_leave_events.send(drag_leave_event); + event_writers.drag_leave_events.write(drag_leave_event); } } @@ -683,7 +698,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_start_event.clone(), *press_target); - event_writers.drag_start_events.send(drag_start_event); + event_writers.drag_start_events.write(drag_start_event); } // Emit Drag events to the entities we are dragging @@ -703,7 +718,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_event.clone(), *drag_target); - event_writers.drag_events.send(drag_event); + event_writers.drag_events.write(drag_event); // Update drag position drag.latest_pos = location.position; @@ -726,7 +741,7 @@ pub fn pointer_events( }, ); commands.trigger_targets(drag_over_event.clone(), hovered_entity); - event_writers.drag_over_events.send(drag_over_event); + event_writers.drag_over_events.write(drag_over_event); } } } @@ -747,7 +762,29 @@ pub fn pointer_events( }, ); commands.trigger_targets(move_event.clone(), hovered_entity); - event_writers.move_events.send(move_event); + event_writers.move_events.write(move_event); + } + } + PointerAction::Scroll { x, y, unit } => { + for (hovered_entity, hit) in hover_map + .get(&pointer_id) + .iter() + .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) + { + // Emit Scroll events to the entities we are hovering + let scroll_event = Pointer::new( + pointer_id, + location.clone(), + hovered_entity, + Scroll { + unit, + x, + y, + hit: hit.clone(), + }, + ); + commands.trigger_targets(scroll_event.clone(), hovered_entity); + event_writers.scroll_events.write(scroll_event); } } // Canceled @@ -761,7 +798,7 @@ pub fn pointer_events( let cancel_event = Pointer::new(pointer_id, location.clone(), hovered_entity, Cancel { hit }); commands.trigger_targets(cancel_event.clone(), hovered_entity); - event_writers.cancel_events.send(cancel_event); + event_writers.cancel_events.write(cancel_event); } // Clear the state for the canceled pointer pointer_state.clear(pointer_id); diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index a0b4ac0830863..02bea5a2584b7 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -14,6 +14,7 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_input::{ + mouse::MouseWheel, prelude::*, touch::{TouchInput, TouchPhase}, ButtonState, @@ -124,7 +125,7 @@ pub fn mouse_pick_events( }, position: event.position, }; - pointer_events.send(PointerInput::new( + pointer_events.write(PointerInput::new( PointerId::Mouse, location, PointerAction::Move { @@ -154,7 +155,24 @@ pub fn mouse_pick_events( ButtonState::Pressed => PointerAction::Press(button), ButtonState::Released => PointerAction::Release(button), }; - pointer_events.send(PointerInput::new(PointerId::Mouse, location, action)); + pointer_events.write(PointerInput::new(PointerId::Mouse, location, action)); + } + WindowEvent::MouseWheel(event) => { + let MouseWheel { unit, x, y, window } = *event; + + let location = Location { + target: match RenderTarget::Window(WindowRef::Entity(window)) + .normalize(primary_window.get_single().ok()) + { + Some(target) => target, + None => continue, + }, + position: *cursor_last, + }; + + let action = PointerAction::Scroll { x, y, unit }; + + pointer_events.write(PointerInput::new(PointerId::Mouse, location, action)); } _ => {} } @@ -189,7 +207,7 @@ pub fn touch_pick_events( debug!("Spawning pointer {:?}", pointer); commands.spawn((pointer, PointerLocation::new(location.clone()))); - pointer_events.send(PointerInput::new( + pointer_events.write(PointerInput::new( pointer, location, PointerAction::Press(PointerButton::Primary), @@ -203,7 +221,7 @@ pub fn touch_pick_events( if last_touch == touch { continue; } - pointer_events.send(PointerInput::new( + pointer_events.write(PointerInput::new( pointer, location, PointerAction::Move { @@ -214,7 +232,7 @@ pub fn touch_pick_events( touch_cache.insert(touch.id, *touch); } TouchPhase::Ended => { - pointer_events.send(PointerInput::new( + pointer_events.write(PointerInput::new( pointer, location, PointerAction::Release(PointerButton::Primary), @@ -222,7 +240,7 @@ pub fn touch_pick_events( touch_cache.remove(&touch.id); } TouchPhase::Canceled => { - pointer_events.send(PointerInput::new( + pointer_events.write(PointerInput::new( pointer, location, PointerAction::Cancel, diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 9e825a38262e2..2fb61142d155f 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -7,7 +7,7 @@ //! allows you to express more complex interactions, like detecting when a touch input drags a UI //! element and drops it on a 3d mesh rendered to a different camera. //! -//! Pointer events bubble up the entity hieararchy and can be used with observers, allowing you to +//! Pointer events bubble up the entity hierarchy and can be used with observers, allowing you to //! succinctly express rich interaction behaviors by attaching pointer callbacks to entities: //! //! ```rust @@ -64,7 +64,7 @@ //! commands.entity(trigger.target()).despawn(); //! }) //! .observe(|trigger: Trigger>, mut events: EventWriter| { -//! events.send(Greeting); +//! events.write(Greeting); //! }); //! } //! ``` @@ -422,6 +422,7 @@ impl Plugin for InteractionPlugin { .add_event::>() .add_event::>() .add_event::>() + .add_event::>() .add_systems( PreUpdate, (generate_hovermap, update_interactions, pointer_events) diff --git a/crates/bevy_picking/src/mesh_picking/mod.rs b/crates/bevy_picking/src/mesh_picking/mod.rs index 57a49a2b0ba60..6007168ee8043 100644 --- a/crates/bevy_picking/src/mesh_picking/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/mod.rs @@ -7,6 +7,11 @@ //! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities. //! //! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter. +//! +//! ## Implementation Notes +//! +//! - The `position` reported in `HitData` is in world space. The `normal` is a vector pointing +//! away from the face, it is not guaranteed to be normalized for scaled meshes. pub mod ray_cast; @@ -123,7 +128,7 @@ pub fn update_hits( .collect::>(); let order = camera.order as f32; if !picks.is_empty() { - output.send(PointerHits::new(ray_id.pointer, picks, order)); + output.write(PointerHits::new(ray_id.pointer, picks, order)); } } } diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index ae5cf9133b98b..dcaaf6060b3f2 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -9,6 +9,7 @@ //! driven by lower-level input devices and consumed by higher-level interaction systems. use bevy_ecs::prelude::*; +use bevy_input::mouse::MouseScrollUnit; use bevy_math::Vec2; use bevy_platform_support::collections::HashMap; use bevy_reflect::prelude::*; @@ -251,6 +252,15 @@ pub enum PointerAction { /// How much the pointer moved from the previous position. delta: Vec2, }, + /// Scroll the pointer + Scroll { + /// The mouse scroll unit. + unit: MouseScrollUnit, + /// The horizontal scroll value. + x: f32, + /// The vertical scroll value. + y: f32, + }, /// Cancel the pointer. Often used for touch events. Cancel, } diff --git a/crates/bevy_picking/src/window.rs b/crates/bevy_picking/src/window.rs index f55edca2dd1f7..30093da79750b 100644 --- a/crates/bevy_picking/src/window.rs +++ b/crates/bevy_picking/src/window.rs @@ -6,6 +6,10 @@ //! window will be inserted as a pointer hit, listed behind all other pointer //! hits. This means that when the pointer isn't hovering any other entities, //! the picking events will be routed to the window. +//! +//! ## Implementation Notes +//! +//! - This backend does not provide `position` or `normal` in `HitData`. use core::f32; @@ -35,7 +39,7 @@ pub fn update_window_hits( { let entity = window_ref.entity(); let hit_data = HitData::new(entity, 0.0, None, None); - output_events.send(PointerHits::new( + output_events.write(PointerHits::new( *pointer_id, vec![(entity, hit_data)], f32::NEG_INFINITY, diff --git a/crates/bevy_platform_support/LICENSE-APACHE b/crates/bevy_platform_support/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_platform_support/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_support/LICENSE-MIT b/crates/bevy_platform_support/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_platform_support/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_ptr/LICENSE-APACHE b/crates/bevy_ptr/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_ptr/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_ptr/LICENSE-MIT b/crates/bevy_ptr/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_ptr/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_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 22daf14f28898..c15784d22d3a0 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -116,7 +116,7 @@ uuid = { version = "1.13.1", default-features = false, optional = true, features "serde", ] } variadics_please = "1.1" -wgpu-types = { version = "23", features = ["serde"], optional = true } +wgpu-types = { version = "24", features = ["serde"], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] uuid = { version = "1.13.1", default-features = false, features = ["js"] } diff --git a/crates/bevy_reflect/LICENSE-APACHE b/crates/bevy_reflect/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_reflect/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_reflect/LICENSE-MIT b/crates/bevy_reflect/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_reflect/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_reflect/derive/LICENSE-APACHE b/crates/bevy_reflect/derive/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_reflect/derive/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_reflect/derive/LICENSE-MIT b/crates/bevy_reflect/derive/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_reflect/derive/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_reflect/derive/src/trait_reflection.rs b/crates/bevy_reflect/derive/src/trait_reflection.rs index 24c452f2f1249..8df40e47a5fd0 100644 --- a/crates/bevy_reflect/derive/src/trait_reflection.rs +++ b/crates/bevy_reflect/derive/src/trait_reflection.rs @@ -1,7 +1,4 @@ -use bevy_macro_utils::{ - fq_std::{FQClone, FQOption, FQResult}, - BevyManifest, -}; +use bevy_macro_utils::fq_std::{FQClone, FQOption, FQResult}; use proc_macro::TokenStream; use quote::quote; use syn::{parse::Parse, parse_macro_input, Attribute, ItemTrait, Token}; @@ -34,7 +31,7 @@ pub(crate) fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStr let trait_ident = &item_trait.ident; let trait_vis = &item_trait.vis; let reflect_trait_ident = crate::ident::get_reflect_ident(&item_trait.ident.to_string()); - let bevy_reflect_path = BevyManifest::shared().get_path("bevy_reflect"); + let bevy_reflect_path = crate::meta::get_bevy_reflect_path(); let struct_doc = format!( " A type generated by the #[reflect_trait] macro for the `{trait_ident}` trait.\n\n This allows casting from `dyn Reflect` to `dyn {trait_ident}`.", diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 3deff6f315c6e..300e69c2a71e9 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,8 +1,8 @@ use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, type_info::impl_type_methods, utility::reflect_hasher, ApplyError, - Generics, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, - ReflectRef, Type, TypeInfo, TypePath, + type_info::impl_type_methods, utility::reflect_hasher, ApplyError, Generics, MaybeTyped, + PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, + TypePath, }; use alloc::{boxed::Box, vec::Vec}; use bevy_reflect_derive::impl_type_path; diff --git a/crates/bevy_reflect/src/attributes.rs b/crates/bevy_reflect/src/attributes.rs index cc952d93f15c8..a6edefab25c65 100644 --- a/crates/bevy_reflect/src/attributes.rs +++ b/crates/bevy_reflect/src/attributes.rs @@ -178,7 +178,6 @@ pub(crate) use impl_custom_attribute_methods; #[cfg(test)] mod tests { use super::*; - use crate as bevy_reflect; use crate::{type_info::Typed, TypeInfo, VariantInfo}; use alloc::{format, string::String}; use core::ops::RangeInclusive; diff --git a/crates/bevy_reflect/src/enums/dynamic_enum.rs b/crates/bevy_reflect/src/enums/dynamic_enum.rs index 9162ab3610eaf..d9e228c07eaae 100644 --- a/crates/bevy_reflect/src/enums/dynamic_enum.rs +++ b/crates/bevy_reflect/src/enums/dynamic_enum.rs @@ -1,9 +1,9 @@ use bevy_reflect_derive::impl_type_path; use crate::{ - self as bevy_reflect, enum_debug, enum_hash, enum_partial_eq, ApplyError, DynamicStruct, - DynamicTuple, Enum, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, - Struct, Tuple, TypeInfo, VariantFieldIter, VariantType, + enum_debug, enum_hash, enum_partial_eq, ApplyError, DynamicStruct, DynamicTuple, Enum, + PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple, + TypeInfo, VariantFieldIter, VariantType, }; use alloc::{boxed::Box, string::String}; diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index 3fe36fc8cfc07..d3f6550640012 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -317,7 +317,6 @@ impl<'a> VariantField<'a> { // Tests that need access to internal fields have to go here rather than in mod.rs #[cfg(test)] mod tests { - use crate as bevy_reflect; use crate::*; #[derive(Reflect, Debug, PartialEq)] diff --git a/crates/bevy_reflect/src/enums/mod.rs b/crates/bevy_reflect/src/enums/mod.rs index 89b5f17b1c8b8..fd657ffa7f25a 100644 --- a/crates/bevy_reflect/src/enums/mod.rs +++ b/crates/bevy_reflect/src/enums/mod.rs @@ -10,7 +10,6 @@ pub use variants::*; #[cfg(test)] mod tests { - use crate as bevy_reflect; use crate::*; use alloc::boxed::Box; diff --git a/crates/bevy_reflect/src/enums/variants.rs b/crates/bevy_reflect/src/enums/variants.rs index 142f95382081e..25e40334e7187 100644 --- a/crates/bevy_reflect/src/enums/variants.rs +++ b/crates/bevy_reflect/src/enums/variants.rs @@ -359,7 +359,6 @@ impl UnitVariantInfo { #[cfg(test)] mod tests { use super::*; - use crate as bevy_reflect; use crate::{Reflect, Typed}; #[test] diff --git a/crates/bevy_reflect/src/func/args/count.rs b/crates/bevy_reflect/src/func/args/count.rs index d5f410f88dfaf..159950ca61110 100644 --- a/crates/bevy_reflect/src/func/args/count.rs +++ b/crates/bevy_reflect/src/func/args/count.rs @@ -264,7 +264,7 @@ mod tests { } #[test] - fn should_allow_removeting_nonexistent_count() { + fn should_allow_removing_nonexistent_count() { let mut count = ArgCount::default(); assert_eq!(count.len(), 0); diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index d7242fd8c6527..c090442ce483f 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -1,5 +1,4 @@ use crate::{ - self as bevy_reflect, __macro_exports::RegisterForReflection, func::{ args::{ArgCount, ArgList}, diff --git a/crates/bevy_reflect/src/func/dynamic_function_internal.rs b/crates/bevy_reflect/src/func/dynamic_function_internal.rs index eb855dc5cee6d..65b98e5e13a67 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_internal.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_internal.rs @@ -246,7 +246,7 @@ mod tests { } #[test] - fn should_merge_overloaed_into_single() { + fn should_merge_overload_into_single() { let mut func_a = DynamicFunctionInternal { functions: vec!['a', 'b'], info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")) diff --git a/crates/bevy_reflect/src/generics.rs b/crates/bevy_reflect/src/generics.rs index 0f413d653dd3c..32b13100f552f 100644 --- a/crates/bevy_reflect/src/generics.rs +++ b/crates/bevy_reflect/src/generics.rs @@ -252,7 +252,6 @@ pub(crate) use impl_generic_info_methods; #[cfg(test)] mod tests { use super::*; - use crate as bevy_reflect; use crate::{Reflect, Typed}; use alloc::string::String; use core::fmt::Debug; diff --git a/crates/bevy_reflect/src/impls/foldhash.rs b/crates/bevy_reflect/src/impls/foldhash.rs index a4f1df44efefc..1b0452d433603 100644 --- a/crates/bevy_reflect/src/impls/foldhash.rs +++ b/crates/bevy_reflect/src/impls/foldhash.rs @@ -1,4 +1,4 @@ -use crate::{self as bevy_reflect, impl_type_path}; +use crate::impl_type_path; impl_type_path!(::foldhash::fast::FoldHasher); impl_type_path!(::foldhash::fast::FixedState); diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 3d3821020bdd8..653eb3edaa55b 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -1,4 +1,3 @@ -use crate as bevy_reflect; use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize}; use assert_type_match::assert_type_match; use bevy_reflect_derive::{impl_reflect, impl_reflect_opaque}; diff --git a/crates/bevy_reflect/src/impls/petgraph.rs b/crates/bevy_reflect/src/impls/petgraph.rs index f9ab09e1b9f26..2264c9cb4b9b2 100644 --- a/crates/bevy_reflect/src/impls/petgraph.rs +++ b/crates/bevy_reflect/src/impls/petgraph.rs @@ -1,7 +1,4 @@ -use crate::{ - self as bevy_reflect, impl_reflect_opaque, prelude::ReflectDefault, ReflectDeserialize, - ReflectSerialize, -}; +use crate::{impl_reflect_opaque, prelude::ReflectDefault, ReflectDeserialize, ReflectSerialize}; impl_reflect_opaque!(::petgraph::graph::NodeIndex( Default, diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 5d92b03181fb9..afb75aff8d997 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -4,10 +4,10 @@ use core::any::Any; use smallvec::{Array as SmallArray, SmallVec}; use crate::{ - self as bevy_reflect, utility::GenericTypeInfoCell, ApplyError, FromReflect, FromType, - Generics, GetTypeRegistration, List, ListInfo, ListIter, MaybeTyped, PartialReflect, Reflect, - ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, - TypePath, TypeRegistration, Typed, + utility::GenericTypeInfoCell, ApplyError, FromReflect, FromType, Generics, GetTypeRegistration, + List, ListInfo, ListIter, MaybeTyped, PartialReflect, Reflect, ReflectFromPtr, ReflectKind, + ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, TypePath, TypeRegistration, + Typed, }; impl List for SmallVec diff --git a/crates/bevy_reflect/src/impls/smol_str.rs b/crates/bevy_reflect/src/impls/smol_str.rs index 7a0f99e2ed7fe..e2c09b206b3be 100644 --- a/crates/bevy_reflect/src/impls/smol_str.rs +++ b/crates/bevy_reflect/src/impls/smol_str.rs @@ -1,6 +1,4 @@ -use crate::{ - self as bevy_reflect, std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize, -}; +use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::impl_reflect_opaque; impl_reflect_opaque!(::smol_str::SmolStr( diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index cb7ee7859978a..447e480f14273 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -4,7 +4,7 @@ )] use crate::{ - self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, + 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, @@ -2441,8 +2441,8 @@ crate::func::macros::impl_function_traits!(&'static Location<'static>); #[cfg(test)] mod tests { use crate::{ - self as bevy_reflect, Enum, FromReflect, PartialReflect, Reflect, ReflectSerialize, - TypeInfo, TypeRegistry, Typed, VariantInfo, VariantType, + Enum, FromReflect, PartialReflect, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, + Typed, VariantInfo, VariantType, }; use alloc::{collections::BTreeMap, string::String, vec}; use bevy_platform_support::collections::HashMap; diff --git a/crates/bevy_reflect/src/impls/uuid.rs b/crates/bevy_reflect/src/impls/uuid.rs index 71f708c4d1da7..56beedd41ab41 100644 --- a/crates/bevy_reflect/src/impls/uuid.rs +++ b/crates/bevy_reflect/src/impls/uuid.rs @@ -1,5 +1,3 @@ -use crate as bevy_reflect; - use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::impl_reflect_opaque; diff --git a/crates/bevy_reflect/src/impls/wgpu_types.rs b/crates/bevy_reflect/src/impls/wgpu_types.rs index 6f99b5d49699d..b4a1750ba1242 100644 --- a/crates/bevy_reflect/src/impls/wgpu_types.rs +++ b/crates/bevy_reflect/src/impls/wgpu_types.rs @@ -1,4 +1,4 @@ -use crate::{self as bevy_reflect, impl_reflect_opaque, ReflectDeserialize, ReflectSerialize}; +use crate::{impl_reflect_opaque, ReflectDeserialize, ReflectSerialize}; impl_reflect_opaque!(::wgpu_types::TextureFormat( Debug, diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 31f0f023bf3fc..2845c716c5d54 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -564,6 +564,9 @@ extern crate std; extern crate alloc; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_reflect; + mod array; mod fields; mod from_reflect; @@ -744,7 +747,6 @@ mod tests { use static_assertions::{assert_impl_all, assert_not_impl_all}; use super::{prelude::*, *}; - use crate as bevy_reflect; use crate::{ serde::{ReflectDeserializer, ReflectSerializer}, utility::GenericTypePathCell, diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 2aff62241f252..e0f019c4b26dc 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -9,9 +9,9 @@ use bevy_reflect_derive::impl_type_path; use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, type_info::impl_type_methods, utility::reflect_hasher, ApplyError, - FromReflect, Generics, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, - ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, + type_info::impl_type_methods, utility::reflect_hasher, ApplyError, FromReflect, Generics, + MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, + TypeInfo, TypePath, }; /// A trait used to power [list-like] operations via [reflection]. diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 0aa1ac7f83915..f71689d598804 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -4,9 +4,9 @@ use bevy_platform_support::collections::HashTable; use bevy_reflect_derive::impl_type_path; use crate::{ - self as bevy_reflect, generics::impl_generic_info_methods, type_info::impl_type_methods, - ApplyError, Generics, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, - ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, + generics::impl_generic_info_methods, type_info::impl_type_methods, ApplyError, Generics, + MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, + TypeInfo, TypePath, }; use alloc::{boxed::Box, format, vec::Vec}; diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index 320c414cdeed2..a52bbb6aaa688 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -512,7 +512,6 @@ impl core::ops::IndexMut for ParsedPath { )] mod tests { use super::*; - use crate as bevy_reflect; use crate::*; use alloc::vec; diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index 562fc6e90ce15..c6907b56ecbe3 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -38,7 +38,6 @@ mod tests { use bevy_platform_support::collections::{HashMap, HashSet}; use crate::{ - self as bevy_reflect, serde::{ ReflectDeserializer, ReflectDeserializerProcessor, ReflectSerializer, TypedReflectDeserializer, diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 0fcc5854a6725..c31d97456679f 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -10,8 +10,8 @@ pub use type_data::*; mod tests { use super::*; use crate::{ - self as bevy_reflect, type_registry::TypeRegistry, DynamicStruct, DynamicTupleStruct, - FromReflect, PartialReflect, Reflect, Struct, + type_registry::TypeRegistry, DynamicStruct, DynamicTupleStruct, FromReflect, + PartialReflect, Reflect, Struct, }; use serde::de::DeserializeSeed; diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index be0ef027e45a3..6cfebc747f285 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -21,7 +21,6 @@ mod tuples; #[cfg(test)] mod tests { use crate::{ - self as bevy_reflect, serde::{ReflectSerializer, ReflectSerializerProcessor}, PartialReflect, Reflect, ReflectSerialize, Struct, TypeRegistry, }; diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs index 8be3f388c9528..52b4e8b9a100e 100644 --- a/crates/bevy_reflect/src/set.rs +++ b/crates/bevy_reflect/src/set.rs @@ -7,9 +7,9 @@ use bevy_platform_support::collections::{ use bevy_reflect_derive::impl_type_path; use crate::{ - self as bevy_reflect, generics::impl_generic_info_methods, hash_error, - type_info::impl_type_methods, ApplyError, Generics, PartialReflect, Reflect, ReflectKind, - ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, + generics::impl_generic_info_methods, hash_error, type_info::impl_type_methods, ApplyError, + Generics, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, + TypeInfo, TypePath, }; /// A trait used to power [set-like] operations via [reflection]. diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 7307b86e6ac83..dc7ed55cefef3 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,6 +1,5 @@ use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, attributes::{impl_custom_attribute_methods, CustomAttributes}, type_info::impl_type_methods, ApplyError, Generics, NamedField, PartialReflect, Reflect, ReflectKind, ReflectMut, @@ -585,7 +584,6 @@ pub fn struct_debug(dyn_struct: &dyn Struct, f: &mut Formatter<'_>) -> core::fmt #[cfg(test)] mod tests { - use crate as bevy_reflect; use crate::*; #[derive(Reflect, Default)] struct MyStruct { diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 9790990a26bb7..28f95b6886cd8 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -3,10 +3,10 @@ use variadics_please::all_tuples; use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, type_info::impl_type_methods, utility::GenericTypePathCell, ApplyError, - FromReflect, Generics, GetTypeRegistration, MaybeTyped, PartialReflect, Reflect, ReflectKind, - ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, TypeRegistration, TypeRegistry, - Typed, UnnamedField, + type_info::impl_type_methods, utility::GenericTypePathCell, ApplyError, FromReflect, Generics, + GetTypeRegistration, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, + ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed, + UnnamedField, }; use alloc::{boxed::Box, vec, vec::Vec}; use core::{ diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index 57cc7acdb377a..8348ca7740861 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -2,7 +2,6 @@ use bevy_reflect_derive::impl_type_path; use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, attributes::{impl_custom_attribute_methods, CustomAttributes}, type_info::impl_type_methods, ApplyError, DynamicTuple, Generics, PartialReflect, Reflect, ReflectKind, ReflectMut, @@ -495,7 +494,6 @@ pub fn tuple_struct_debug( #[cfg(test)] mod tests { - use crate as bevy_reflect; use crate::*; #[derive(Reflect)] struct Ts(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8); diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index ee7e31f84a925..cf80749edebbb 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -165,6 +165,43 @@ impl TypeRegistry { } } + /// Attempts to register the referenced type `T` if it has not yet been registered. + /// + /// See [`register`] for more details. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::{Reflect, TypeRegistry}; + /// # use core::any::TypeId; + /// # + /// # let mut type_registry = TypeRegistry::default(); + /// # + /// #[derive(Reflect)] + /// struct Foo { + /// bar: Bar, + /// } + /// + /// #[derive(Reflect)] + /// struct Bar; + /// + /// let foo = Foo { bar: Bar }; + /// + /// // Equivalent to `type_registry.register::()` + /// type_registry.register_by_val(&foo); + /// + /// assert!(type_registry.contains(TypeId::of::())); + /// assert!(type_registry.contains(TypeId::of::())); + /// ``` + /// + /// [`register`]: Self::register + pub fn register_by_val(&mut self, _: &T) + where + T: GetTypeRegistration, + { + self.register::(); + } + /// Attempts to register the type described by `registration`. /// /// If the registration for the type already exists, it will not be registered again. @@ -859,7 +896,6 @@ impl FromType for ReflectFromPtr { )] mod test { use super::*; - use crate as bevy_reflect; #[test] fn test_reflect_from_ptr() { diff --git a/crates/bevy_remote/LICENSE-APACHE b/crates/bevy_remote/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_remote/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_remote/LICENSE-MIT b/crates/bevy_remote/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_remote/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_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 4962125622270..4167185632fa3 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -24,12 +24,15 @@ shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"] # Enable SPIR-V shader passthrough spirv_shader_passthrough = ["wgpu/spirv"] +# Statically linked DXC shader compiler for DirectX 12 +# TODO: When wgpu switches to DirectX 12 instead of Vulkan by default on windows, make this a default feature +statically-linked-dxc = ["wgpu/static-dxc"] + trace = ["profiling"] tracing-tracy = [] ci_limits = [] webgl = ["wgpu/webgl"] webgpu = ["wgpu/webgpu"] -ios_simulator = [] detailed_trace = [] [dependencies] @@ -70,14 +73,14 @@ codespan-reporting = "0.11.0" # It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm. # When the 'atomics' feature is enabled `fragile-send-sync-non-atomic` does nothing # and Bevy instead wraps `wgpu` types to verify they are not used off their origin thread. -wgpu = { version = "23.0.1", default-features = false, features = [ +wgpu = { version = "24", default-features = false, features = [ "wgsl", "dx12", "metal", "naga-ir", "fragile-send-sync-non-atomic-wasm", ] } -naga = { version = "23", features = ["wgsl-in"] } +naga = { version = "24", features = ["wgsl-in"] } serde = { version = "1", features = ["derive"] } bytemuck = { version = "1.5", features = ["derive", "must_cast"] } downcast-rs = { version = "2", default-features = false, features = ["std"] } @@ -96,15 +99,21 @@ smallvec = { version = "1.11", features = ["const_new"] } offset-allocator = "0.2" variadics_please = "1.1" tracing = { version = "0.1", default-features = false, features = ["std"] } +indexmap = { version = "2" } +fixedbitset = { version = "0.5" } +bitflags = "2" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. -naga_oil = { version = "0.16", default-features = false, features = [ +naga_oil = { version = "0.17", default-features = false, features = [ "test_shader", ] } +[dev-dependencies] +proptest = "1" + [target.'cfg(target_arch = "wasm32")'.dependencies] -naga_oil = "0.16" +naga_oil = "0.17" js-sys = "0.3" web-sys = { version = "0.3.67", features = [ 'Blob', diff --git a/crates/bevy_render/LICENSE-APACHE b/crates/bevy_render/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_render/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_render/LICENSE-MIT b/crates/bevy_render/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_render/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_render/macros/LICENSE-APACHE b/crates/bevy_render/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_render/macros/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_render/macros/LICENSE-MIT b/crates/bevy_render/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_render/macros/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_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index d5ce58d46c107..47cf8b7ce8aef 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -3,10 +3,11 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{ + parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, token::Comma, - Data, DataStruct, Error, Fields, Lit, LitInt, LitStr, Meta, MetaList, Result, + Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, MetaList, Result, }; const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform"); @@ -16,6 +17,8 @@ const SAMPLER_ATTRIBUTE_NAME: Symbol = Symbol("sampler"); const STORAGE_ATTRIBUTE_NAME: Symbol = Symbol("storage"); const BIND_GROUP_DATA_ATTRIBUTE_NAME: Symbol = Symbol("bind_group_data"); const BINDLESS_ATTRIBUTE_NAME: Symbol = Symbol("bindless"); +const BINDING_ARRAY_MODIFIER_NAME: Symbol = Symbol("binding_array"); +const LIMIT_MODIFIER_NAME: Symbol = Symbol("limit"); #[derive(Copy, Clone, Debug)] enum BindingType { @@ -39,6 +42,11 @@ enum BindingState<'a> { }, } +enum BindlessSlabResourceLimitAttr { + Auto, + Limit(LitInt), +} + pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let manifest = BevyManifest::shared(); let render_path = manifest.get_path("bevy_render"); @@ -48,14 +56,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let mut binding_states: Vec = Vec::new(); let mut binding_impls = Vec::new(); - let mut binding_layouts = Vec::new(); + let mut bindless_binding_layouts = Vec::new(); + let mut non_bindless_binding_layouts = Vec::new(); + let mut bindless_resource_types = Vec::new(); + let mut bindless_buffer_descriptors = Vec::new(); let mut attr_prepared_data_ident = None; + // 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; // `actual_bindless_slot_count` holds the actual number of bindless slots // per bind group, taking into account whether the current platform supports // bindless resources. let actual_bindless_slot_count = Ident::new("actual_bindless_slot_count", Span::call_site()); + let bind_group_layout_entries = Ident::new("bind_group_layout_entries", Span::call_site()); // The `BufferBindingType` and corresponding `BufferUsages` used for // uniforms. We need this because bindless uniforms don't exist, so in @@ -63,7 +77,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let uniform_binding_type = Ident::new("uniform_binding_type", Span::call_site()); let uniform_buffer_usages = Ident::new("uniform_buffer_usages", Span::call_site()); - // Read struct-level attributes + // Read struct-level attributes, first pass. for attr in &ast.attrs { if let Some(attr_ident) = attr.path().get_ident() { if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME { @@ -72,8 +86,43 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { { attr_prepared_data_ident = Some(prepared_data_ident); } - } else if attr_ident == UNIFORM_ATTRIBUTE_NAME { - let (binding_index, converted_shader_type) = get_uniform_binding_attr(attr)?; + } 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::Limit(lit)); + return Ok(()); + } + + Err(Error::new_spanned(attr, "Expected `limit(N)`")) + })?; + } + _ => {} + } + } + } + } + + // Read struct-level attributes, second pass. + for attr in &ast.attrs { + if let Some(attr_ident) = attr.path().get_ident() { + if attr_ident == UNIFORM_ATTRIBUTE_NAME { + let UniformBindingAttr { + binding_index, + converted_shader_type, + binding_array: binding_array_binding, + } = get_uniform_binding_attr(attr)?; binding_impls.push(quote! {{ use #render_path::render_resource::AsBindGroupShaderType; let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); @@ -91,30 +140,89 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }}); - binding_layouts.push(quote!{ - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #render_path::render_resource::ShaderStages::all(), - ty: #render_path::render_resource::BindingType::Buffer { - ty: #uniform_binding_type, - has_dynamic_offset: false, - min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()), - }, - count: #actual_bindless_slot_count, - } + // Push the binding layout. This depends on whether we're bindless or not. + + non_bindless_binding_layouts.push(quote!{ + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #render_path::render_resource::ShaderStages::all(), + ty: #render_path::render_resource::BindingType::Buffer { + ty: #uniform_binding_type, + has_dynamic_offset: false, + min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()), + }, + count: None, + } + ); }); + match binding_array_binding { + None => { + if attr_bindless_count.is_some() { + return Err(Error::new_spanned( + attr, + "Must specify `binding_array(...)` with `#[uniform]` if the \ + object is bindless", + )); + } + } + Some(binding_array_binding) => { + if attr_bindless_count.is_none() { + return Err(Error::new_spanned( + attr, + "`binding_array(...)` with `#[uniform]` requires the object to be \ + bindless", + )); + } + + bindless_binding_layouts.push(quote!{ + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_array_binding, + visibility: #render_path::render_resource::ShaderStages::all(), + ty: #render_path::render_resource::BindingType::Buffer { + ty: #uniform_binding_type, + has_dynamic_offset: false, + min_binding_size: Some(<#converted_shader_type as #render_path::render_resource::ShaderType>::min_size()), + }, + count: #actual_bindless_slot_count, + } + ); + }); + + bindless_buffer_descriptors.push(quote! { + #render_path::render_resource::BindlessBufferDescriptor { + // Note that, because this is bindless, *binding + // index* here refers to the index in the + // bindless index table (`bindless_index`), and + // the actual binding number is the *binding + // array binding*. + binding_number: #render_path::render_resource::BindingNumber( + #binding_array_binding + ), + bindless_index: + #render_path::render_resource::BindlessIndex(#binding_index), + size: <#converted_shader_type as + #render_path::render_resource::ShaderType>::min_size().get() as + usize, + } + }); + + add_bindless_resource_type( + &render_path, + &mut bindless_resource_types, + binding_index, + quote! { #render_path::render_resource::BindlessResourceType::Buffer }, + ); + } + } + let required_len = binding_index as usize + 1; if required_len > binding_states.len() { binding_states.resize(required_len, BindingState::Free); } binding_states[binding_index as usize] = BindingState::OccupiedConvertedUniform; - } else if attr_ident == BINDLESS_ATTRIBUTE_NAME { - if let Ok(count_lit) = - attr.parse_args_with(|input: ParseStream| input.parse::()) - { - attr_bindless_count = Some(count_lit); - } } } } @@ -135,7 +243,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { // Count the number of sampler fields needed. We might have to disable // bindless if bindless arrays take the GPU over the maximum number of // samplers. - let mut sampler_binding_count = 0; + let mut sampler_binding_count: u32 = 0; // Read field-level attributes for field in fields { @@ -225,9 +333,25 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { match binding_type { BindingType::Uniform => { + if attr_bindless_count.is_some() { + return Err(Error::new_spanned( + attr, + "Only structure-level `#[uniform]` attributes are supported in \ + bindless mode", + )); + } + // uniform codegen is deferred to account for combined uniform bindings } + BindingType::Storage => { + if attr_bindless_count.is_some() { + return Err(Error::new_spanned( + attr, + "Storage buffers are unsupported in bindless mode", + )); + } + let StorageAttrs { visibility, read_only, @@ -259,20 +383,32 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { }); } - binding_layouts.push(quote! { - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #visibility, - ty: #render_path::render_resource::BindingType::Buffer { - ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: #actual_bindless_slot_count, - } + // TODO: Support bindless buffers that aren't + // structure-level `#[uniform]` attributes. + non_bindless_binding_layouts.push(quote! { + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #visibility, + ty: #render_path::render_resource::BindingType::Buffer { + ty: #render_path::render_resource::BufferBindingType::Storage { read_only: #read_only }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: #actual_bindless_slot_count, + } + ); }); } + BindingType::StorageTexture => { + if attr_bindless_count.is_some() { + return Err(Error::new_spanned( + attr, + "Storage textures are unsupported in bindless mode", + )); + } + let StorageTextureAttrs { dimension, image_format, @@ -302,19 +438,22 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }); - binding_layouts.push(quote! { - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #visibility, - ty: #render_path::render_resource::BindingType::StorageTexture { - access: #render_path::render_resource::StorageTextureAccess::#access, - format: #render_path::render_resource::TextureFormat::#image_format, - view_dimension: #render_path::render_resource::#dimension, - }, - count: #actual_bindless_slot_count, - } + non_bindless_binding_layouts.push(quote! { + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #visibility, + ty: #render_path::render_resource::BindingType::StorageTexture { + access: #render_path::render_resource::StorageTextureAccess::#access, + format: #render_path::render_resource::TextureFormat::#image_format, + view_dimension: #render_path::render_resource::#dimension, + }, + count: #actual_bindless_slot_count, + } + ); }); } + BindingType::Texture => { let TextureAttrs { dimension, @@ -348,19 +487,64 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { sampler_binding_count += 1; - binding_layouts.push(quote! { - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #visibility, - ty: #render_path::render_resource::BindingType::Texture { - multisampled: #multisampled, - sample_type: #render_path::render_resource::#sample_type, - view_dimension: #render_path::render_resource::#dimension, - }, - count: #actual_bindless_slot_count, - } + non_bindless_binding_layouts.push(quote! { + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #visibility, + ty: #render_path::render_resource::BindingType::Texture { + multisampled: #multisampled, + sample_type: #render_path::render_resource::#sample_type, + view_dimension: #render_path::render_resource::#dimension, + }, + count: #actual_bindless_slot_count, + } + ); }); + + let bindless_resource_type = match *dimension { + BindingTextureDimension::D1 => { + quote! { + #render_path::render_resource::BindlessResourceType::Texture1d + } + } + BindingTextureDimension::D2 => { + quote! { + #render_path::render_resource::BindlessResourceType::Texture2d + } + } + BindingTextureDimension::D2Array => { + quote! { + #render_path::render_resource::BindlessResourceType::Texture2dArray + } + } + BindingTextureDimension::Cube => { + quote! { + #render_path::render_resource::BindlessResourceType::TextureCube + } + } + BindingTextureDimension::CubeArray => { + quote! { + #render_path::render_resource::BindlessResourceType::TextureCubeArray + } + } + BindingTextureDimension::D3 => { + quote! { + #render_path::render_resource::BindlessResourceType::Texture3d + } + } + }; + + // Add the texture to the `BindlessResourceType` list in the + // bindless descriptor. + add_bindless_resource_type( + &render_path, + &mut bindless_resource_types, + binding_index, + bindless_resource_type, + ); } + BindingType::Sampler => { let SamplerAttrs { sampler_binding_type, @@ -394,7 +578,10 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { binding_impls.insert(0, quote! { ( #binding_index, - #render_path::render_resource::OwnedBindingResource::Sampler({ + #render_path::render_resource::OwnedBindingResource::Sampler( + // TODO: Support other types. + #render_path::render_resource::WgpuSamplerBindingType::Filtering, + { let handle: Option<&#asset_path::Handle<#image_path::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?; @@ -426,14 +613,29 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { sampler_binding_count += 1; - binding_layouts.push(quote!{ - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #visibility, - ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type), - count: #actual_bindless_slot_count, - } + non_bindless_binding_layouts.push(quote!{ + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #visibility, + ty: #render_path::render_resource::BindingType::Sampler(#render_path::render_resource::#sampler_binding_type), + count: #actual_bindless_slot_count, + } + ); }); + + // Add the sampler to the `BindlessResourceType` list in the + // bindless descriptor. + // + // TODO: Support other types of samplers. + add_bindless_resource_type( + &render_path, + &mut bindless_resource_types, + binding_index, + quote! { + #render_path::render_resource::BindlessResourceType::SamplerFiltering + }, + ); } } } @@ -495,17 +697,19 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }}); - binding_layouts.push(quote!{ - #render_path::render_resource::BindGroupLayoutEntry { - binding: #binding_index, - visibility: #render_path::render_resource::ShaderStages::all(), - ty: #render_path::render_resource::BindingType::Buffer { - ty: #uniform_binding_type, - has_dynamic_offset: false, - min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), - }, - count: actual_bindless_slot_count, - } + non_bindless_binding_layouts.push(quote!{ + #bind_group_layout_entries.push( + #render_path::render_resource::BindGroupLayoutEntry { + binding: #binding_index, + visibility: #render_path::render_resource::ShaderStages::all(), + ty: #render_path::render_resource::BindingType::Buffer { + ty: #uniform_binding_type, + has_dynamic_offset: false, + min_binding_size: Some(<#field_ty as #render_path::render_resource::ShaderType>::min_size()), + }, + count: #actual_bindless_slot_count, + } + ); }); // multi-field uniform bindings for a given index require an intermediate struct to derive ShaderType } else { @@ -541,8 +745,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { ) }}); - binding_layouts.push(quote!{ - #render_path::render_resource::BindGroupLayoutEntry { + non_bindless_binding_layouts.push(quote!{ + #bind_group_layout_entries.push(#render_path::render_resource::BindGroupLayoutEntry { binding: #binding_index, visibility: #render_path::render_resource::ShaderStages::all(), ty: #render_path::render_resource::BindingType::Buffer { @@ -550,8 +754,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { has_dynamic_offset: false, min_binding_size: Some(<#uniform_struct_name as #render_path::render_resource::ShaderType>::min_size()), }, - count: actual_bindless_slot_count, - } + count: #actual_bindless_slot_count, + }); }); } } @@ -571,46 +775,100 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { // Calculate the number of samplers that we need, so that we don't go over // the limit on certain platforms. See // https://github.com/bevyengine/bevy/issues/16988. - let samplers_needed = match attr_bindless_count { - Some(Lit::Int(ref bindless_count)) => match bindless_count.base10_parse::() { - Ok(bindless_count) => sampler_binding_count * bindless_count, - Err(_) => 0, - }, - _ => 0, + let bindless_count_syntax = match attr_bindless_count { + Some(BindlessSlabResourceLimitAttr::Auto) => { + quote! { #render_path::render_resource::AUTO_BINDLESS_SLAB_RESOURCE_LIMIT } + } + Some(BindlessSlabResourceLimitAttr::Limit(ref count)) => { + quote! { #count } + } + None => quote! { 0 }, }; // Calculate the actual number of bindless slots, taking hardware // limitations into account. - let (bindless_slot_count, actual_bindless_slot_count_declaration) = match attr_bindless_count { - Some(bindless_count) => ( - quote! { - fn bindless_slot_count() -> Option { - Some(#bindless_count) - } + let (bindless_slot_count, actual_bindless_slot_count_declaration, bindless_descriptor_syntax) = + match attr_bindless_count { + Some(ref bindless_count) => { + let bindless_supported_syntax = quote! { + fn bindless_supported( + render_device: &#render_path::renderer::RenderDevice + ) -> bool { + render_device.features().contains( + #render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY | + #render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY + ) && + render_device.limits().max_storage_buffers_per_shader_stage > 0 && + render_device.limits().max_samplers_per_shader_stage >= + (#sampler_binding_count * #bindless_count_syntax) + } + }; + let actual_bindless_slot_count_declaration = quote! { + let #actual_bindless_slot_count = if Self::bindless_supported(render_device) && + !force_no_bindless { + ::core::num::NonZeroU32::new(#bindless_count_syntax) + } else { + None + }; + }; + let bindless_slot_count_declaration = match bindless_count { + BindlessSlabResourceLimitAttr::Auto => { + quote! { + fn bindless_slot_count() -> Option< + #render_path::render_resource::BindlessSlabResourceLimit + > { + Some(#render_path::render_resource::BindlessSlabResourceLimit::Auto) + } + } + } + BindlessSlabResourceLimitAttr::Limit(lit) => { + quote! { + fn bindless_slot_count() -> Option< + #render_path::render_resource::BindlessSlabResourceLimit + > { + Some(#render_path::render_resource::BindlessSlabResourceLimit::Custom(#lit)) + } + } + } + }; - fn bindless_supported(render_device: &#render_path::renderer::RenderDevice) -> bool { - render_device.features().contains( - #render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY | - #render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY - ) && - render_device.limits().max_storage_buffers_per_shader_stage > 0 && - render_device.limits().max_samplers_per_shader_stage >= #samplers_needed - } - }, - quote! { - let #actual_bindless_slot_count = if Self::bindless_supported(render_device) && - !force_no_bindless { - ::core::num::NonZeroU32::new(#bindless_count) - } else { - None + let bindless_buffer_descriptor_count = bindless_buffer_descriptors.len(); + + // We use `LazyLock` so that we can call `min_size`, which isn't + // a `const fn`. + let bindless_descriptor_syntax = quote! { + static RESOURCES: &[#render_path::render_resource::BindlessResourceType] = &[ + #(#bindless_resource_types),* + ]; + static BUFFERS: ::std::sync::LazyLock<[ + #render_path::render_resource::BindlessBufferDescriptor; + #bindless_buffer_descriptor_count + ]> = ::std::sync::LazyLock::new(|| { + [#(#bindless_buffer_descriptors),*] + }); + Some(#render_path::render_resource::BindlessDescriptor { + resources: ::std::borrow::Cow::Borrowed(RESOURCES), + buffers: ::std::borrow::Cow::Borrowed(&*BUFFERS), + }) }; - }, - ), - None => ( - TokenStream::new().into(), - quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; }, - ), - }; + + ( + quote! { + #bindless_slot_count_declaration + #bindless_supported_syntax + }, + actual_bindless_slot_count_declaration, + bindless_descriptor_syntax, + ) + } + None => ( + TokenStream::new().into(), + quote! { let #actual_bindless_slot_count: Option<::core::num::NonZeroU32> = None; }, + quote! { None }, + ), + }; + + let bindless_resource_count = bindless_resource_types.len() as u32; Ok(TokenStream::from(quote! { #(#field_struct_impls)* @@ -654,12 +912,54 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { #actual_bindless_slot_count_declaration #uniform_binding_type_declarations - vec![#(#binding_layouts,)*] + let mut #bind_group_layout_entries = Vec::new(); + match #actual_bindless_slot_count { + Some(bindless_slot_count) => { + #bind_group_layout_entries.extend( + #render_path::render_resource::create_bindless_bind_group_layout_entries( + #bindless_resource_count, + bindless_slot_count.into(), + ).into_iter() + ); + #(#bindless_binding_layouts)*; + } + None => { + #(#non_bindless_binding_layouts)*; + } + }; + #bind_group_layout_entries + } + + fn bindless_descriptor() -> Option<#render_path::render_resource::BindlessDescriptor> { + #bindless_descriptor_syntax } } })) } +/// Adds a bindless resource type to the `BindlessResourceType` array in the +/// bindless descriptor we're building up. +/// +/// See the `bevy_render::render_resource::bindless::BindlessResourceType` +/// documentation for more information. +fn add_bindless_resource_type( + render_path: &syn::Path, + bindless_resource_types: &mut Vec, + binding_index: u32, + bindless_resource_type: proc_macro2::TokenStream, +) { + // If we need to grow the array, pad the unused fields with + // `BindlessResourceType::None`. + if bindless_resource_types.len() < (binding_index as usize + 1) { + bindless_resource_types.resize_with(binding_index as usize + 1, || { + quote! { #render_path::render_resource::BindlessResourceType::None } + }); + } + + // Assign the `BindlessResourceType`. + bindless_resource_types[binding_index as usize] = bindless_resource_type; +} + fn get_fallback_image( render_path: &syn::Path, dimension: BindingTextureDimension, @@ -682,8 +982,21 @@ fn get_fallback_image( /// like `#[uniform(LitInt, Ident)]` struct UniformBindingMeta { lit_int: LitInt, - _comma: Comma, ident: Ident, + binding_array: Option, +} + +/// The parsed structure-level `#[uniform]` attribute. +/// +/// The corresponding syntax is `#[uniform(BINDING_INDEX, CONVERTED_SHADER_TYPE, +/// binding_array(BINDING_ARRAY)]`. +struct UniformBindingAttr { + /// The binding index. + binding_index: u32, + /// The uniform data type. + converted_shader_type: Ident, + /// The binding number of the binding array, if this is a bindless material. + binding_array: Option, } /// Represents the arguments for any general binding attribute. @@ -725,22 +1038,52 @@ impl Parse for BindingIndexOptions { } impl Parse for UniformBindingMeta { + // Parse syntax like `#[uniform(0, StandardMaterial, binding_array(10))]`. fn parse(input: ParseStream) -> Result { + let lit_int = input.parse()?; + input.parse::()?; + let ident = input.parse()?; + + // Look for a `binding_array(BINDING_NUMBER)` declaration. + let mut binding_array: Option = None; + if input.parse::().is_ok() { + if input + .parse::()? + .get_ident() + .is_none_or(|ident| *ident != BINDING_ARRAY_MODIFIER_NAME) + { + return Err(Error::new_spanned(ident, "Expected `binding_array`")); + } + let parser; + parenthesized!(parser in input); + binding_array = Some(parser.parse()?); + } + Ok(Self { - lit_int: input.parse()?, - _comma: input.parse()?, - ident: input.parse()?, + lit_int, + ident, + binding_array, }) } } -fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<(u32, Ident)> { +/// Parses a structure-level `#[uniform]` attribute (not a field-level +/// `#[uniform]` attribute). +fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result { let uniform_binding_meta = attr.parse_args_with(UniformBindingMeta::parse)?; let binding_index = uniform_binding_meta.lit_int.base10_parse()?; let ident = uniform_binding_meta.ident; + let binding_array = match uniform_binding_meta.binding_array { + None => None, + Some(binding_array) => Some(binding_array.base10_parse()?), + }; - Ok((binding_index, ident)) + Ok(UniformBindingAttr { + binding_index, + converted_shader_type: ident, + binding_array, + }) } fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec)> { diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 5f88f589ebca1..15b1add6b23b8 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -11,10 +11,7 @@ use quote::format_ident; use syn::{parse_macro_input, DeriveInput}; pub(crate) fn bevy_render_path() -> syn::Path { - BevyManifest::shared() - .maybe_get_path("bevy_render") - // NOTE: If the derivation is within bevy_render, then we need to return 'crate' - .unwrap_or_else(|| BevyManifest::parse_str("crate")) + BevyManifest::shared().get_path("bevy_render") } #[proc_macro_derive(ExtractResource)] diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index ff5f79f43dd6d..661bc901f41b9 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -1,10 +1,11 @@ //! Batching functionality when GPU preprocessing is in use. -use core::any::TypeId; +use core::{any::TypeId, marker::PhantomData, mem}; use bevy_app::{App, Plugin}; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity}, + prelude::Entity, query::{Has, With}, resource::Resource, schedule::IntoSystemConfigs as _, @@ -13,9 +14,11 @@ use bevy_ecs::{ }; use bevy_encase_derive::ShaderType; use bevy_math::UVec4; -use bevy_platform_support::collections::hash_map::Entry; +use bevy_platform_support::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 wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features}; @@ -24,26 +27,24 @@ use crate::{ experimental::occlusion_culling::OcclusionCulling, render_phase::{ BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet, - BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItemBatchSetKey as _, - PhaseItemExtraIndex, SortedPhaseItem, SortedRenderPhase, UnbatchableBinnedEntityIndices, - ViewBinnedRenderPhases, ViewSortedRenderPhases, + BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItem, + PhaseItemBatchSetKey as _, PhaseItemExtraIndex, RenderBin, SortedPhaseItem, + SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases, + ViewSortedRenderPhases, }, - render_resource::{Buffer, BufferVec, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, + render_resource::{Buffer, GpuArrayBufferable, RawBufferVec, UninitBufferVec}, renderer::{RenderAdapter, RenderDevice, RenderQueue}, - view::{ExtractedView, NoIndirectDrawing}, - Render, RenderApp, RenderSet, + sync_world::MainEntity, + view::{ExtractedView, NoIndirectDrawing, RetainedViewEntity}, + Render, RenderApp, RenderDebugFlags, RenderSet, }; use super::{BatchMeta, GetBatchData, GetFullBatchData}; #[derive(Default)] pub struct BatchingPlugin { - /// If true, this sets the `COPY_SRC` flag on indirect draw parameters so - /// that they can be read back to CPU. - /// - /// This is a debugging feature that may reduce performance. It primarily - /// exists for the `occlusion_culling` example. - pub allow_copies_from_indirect_parameters: bool, + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, } impl Plugin for BatchingPlugin { @@ -54,7 +55,8 @@ impl Plugin for BatchingPlugin { render_app .insert_resource(IndirectParametersBuffers::new( - self.allow_copies_from_indirect_parameters, + self.debug_flags + .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), )) .add_systems( Render, @@ -147,18 +149,6 @@ where BD: GpuArrayBufferable + Sync + Send + 'static, BDI: Pod + Default, { - /// A storage area for the buffer data that the GPU compute shader is - /// expected to write to. - /// - /// There will be one entry for each index. - pub data_buffer: UninitBufferVec, - - /// The index of the buffer data in the current input buffer that - /// corresponds to each instance. - /// - /// This is keyed off each view. Each view has a separate buffer. - pub work_item_buffers: EntityHashMap>, - /// The uniform data inputs for the current frame. /// /// These are uploaded during the extraction phase. @@ -173,6 +163,81 @@ where /// corresponding buffer data input uniform in this list. pub previous_input_buffer: InstanceInputUniformBuffer, + /// The data needed to render buffers for each phase. + /// + /// The keys of this map are the type IDs of each phase: e.g. `Opaque3d`, + /// `AlphaMask3d`, etc. + pub phase_instance_buffers: TypeIdMap>, +} + +impl Default for BatchedInstanceBuffers +where + BD: GpuArrayBufferable + Sync + Send + 'static, + BDI: Pod + Sync + Send + Default + 'static, +{ + fn default() -> Self { + BatchedInstanceBuffers { + current_input_buffer: InstanceInputUniformBuffer::new(), + previous_input_buffer: InstanceInputUniformBuffer::new(), + phase_instance_buffers: HashMap::default(), + } + } +} + +/// The GPU buffers holding the data needed to render batches for a single +/// phase. +/// +/// These are split out per phase so that we can run the phases in parallel. +/// This is the version of the structure that has a type parameter, which +/// enables Bevy's scheduler to run the batching operations for the different +/// phases in parallel. +/// +/// See the documentation for [`BatchedInstanceBuffers`] for more information. +#[derive(Resource)] +pub struct PhaseBatchedInstanceBuffers +where + PI: PhaseItem, + BD: GpuArrayBufferable + Sync + Send + 'static, +{ + /// The buffers for this phase. + pub buffers: UntypedPhaseBatchedInstanceBuffers, + phantom: PhantomData, +} + +impl Default for PhaseBatchedInstanceBuffers +where + PI: PhaseItem, + BD: GpuArrayBufferable + Sync + Send + 'static, +{ + fn default() -> Self { + PhaseBatchedInstanceBuffers { + buffers: UntypedPhaseBatchedInstanceBuffers::default(), + phantom: PhantomData, + } + } +} + +/// The GPU buffers holding the data needed to render batches for a single +/// phase, without a type parameter for that phase. +/// +/// Since this structure doesn't have a type parameter, it can be placed in +/// [`BatchedInstanceBuffers::phase_instance_buffers`]. +pub struct UntypedPhaseBatchedInstanceBuffers +where + BD: GpuArrayBufferable + Sync + Send + 'static, +{ + /// A storage area for the buffer data that the GPU compute shader is + /// expected to write to. + /// + /// There will be one entry for each index. + pub data_buffer: UninitBufferVec, + + /// The index of the buffer data in the current input buffer that + /// corresponds to each instance. + /// + /// This is keyed off each view. Each view has a separate buffer. + pub work_item_buffers: HashMap, + /// A buffer that holds the number of indexed meshes that weren't visible in /// the previous frame, when GPU occlusion culling is in use. /// @@ -327,7 +392,7 @@ pub enum PreprocessWorkItemBuffers { /// /// Because we don't have to separate indexed from non-indexed meshes in /// direct mode, we only have a single buffer here. - Direct(BufferVec), + Direct(RawBufferVec), /// The buffer of work items we use if we are using indirect drawing. /// @@ -336,9 +401,9 @@ pub enum PreprocessWorkItemBuffers { /// different sizes. Indirect { /// The buffer of work items corresponding to indexed meshes. - indexed: BufferVec, + indexed: RawBufferVec, /// The buffer of work items corresponding to non-indexed meshes. - non_indexed: BufferVec, + non_indexed: RawBufferVec, /// The work item buffers we use when GPU occlusion culling is in use. gpu_occlusion_culling: Option, }, @@ -351,11 +416,11 @@ pub struct GpuOcclusionCullingWorkItemBuffers { /// The buffer of work items corresponding to non-indexed meshes. pub late_non_indexed: UninitBufferVec, /// The offset into the - /// [`BatchedInstanceBuffers::late_indexed_indirect_parameters_buffer`] + /// [`UntypedPhaseBatchedInstanceBuffers::late_indexed_indirect_parameters_buffer`] /// where this view's indirect dispatch counts for indexed meshes live. pub late_indirect_parameters_indexed_offset: u32, /// The offset into the - /// [`BatchedInstanceBuffers::late_non_indexed_indirect_parameters_buffer`] + /// [`UntypedPhaseBatchedInstanceBuffers::late_non_indexed_indirect_parameters_buffer`] /// where this view's indirect dispatch counts for non-indexed meshes live. pub late_indirect_parameters_non_indexed_offset: u32, } @@ -409,56 +474,89 @@ impl Default for LatePreprocessWorkItemIndirectParameters { /// You may need to call this function if you're implementing your own custom /// render phases. See the `specialized_mesh_pipeline` example. pub fn get_or_create_work_item_buffer<'a, I>( - work_item_buffers: &'a mut EntityHashMap>, - view: Entity, + work_item_buffers: &'a mut HashMap, + view: RetainedViewEntity, no_indirect_drawing: bool, - gpu_occlusion_culling: bool, - late_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< - LatePreprocessWorkItemIndirectParameters, - >, - late_non_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< - LatePreprocessWorkItemIndirectParameters, - >, + enable_gpu_occlusion_culling: bool, ) -> &'a mut PreprocessWorkItemBuffers where I: 'static, { - match work_item_buffers - .entry(view) - .or_default() - .entry(TypeId::of::()) - { + let preprocess_work_item_buffers = match work_item_buffers.entry(view) { Entry::Occupied(occupied_entry) => occupied_entry.into_mut(), Entry::Vacant(vacant_entry) => { if no_indirect_drawing { - vacant_entry.insert(PreprocessWorkItemBuffers::Direct(BufferVec::new( + vacant_entry.insert(PreprocessWorkItemBuffers::Direct(RawBufferVec::new( BufferUsages::STORAGE, ))) } else { vacant_entry.insert(PreprocessWorkItemBuffers::Indirect { - indexed: BufferVec::new(BufferUsages::STORAGE), - non_indexed: BufferVec::new(BufferUsages::STORAGE), - gpu_occlusion_culling: if gpu_occlusion_culling { - let late_indirect_parameters_indexed_offset = - late_indexed_indirect_parameters_buffer - .push(LatePreprocessWorkItemIndirectParameters::default()); - let late_indirect_parameters_non_indexed_offset = - late_non_indexed_indirect_parameters_buffer - .push(LatePreprocessWorkItemIndirectParameters::default()); - Some(GpuOcclusionCullingWorkItemBuffers { - late_indexed: UninitBufferVec::new(BufferUsages::STORAGE), - late_non_indexed: UninitBufferVec::new(BufferUsages::STORAGE), - late_indirect_parameters_indexed_offset: - late_indirect_parameters_indexed_offset as u32, - late_indirect_parameters_non_indexed_offset: - late_indirect_parameters_non_indexed_offset as u32, - }) - } else { - None - }, + indexed: RawBufferVec::new(BufferUsages::STORAGE), + non_indexed: RawBufferVec::new(BufferUsages::STORAGE), + // We fill this in below if `enable_gpu_occlusion_culling` + // is set. + gpu_occlusion_culling: None, }) } } + }; + + // Initialize the GPU occlusion culling buffers if necessary. + if let PreprocessWorkItemBuffers::Indirect { + ref mut gpu_occlusion_culling, + .. + } = *preprocess_work_item_buffers + { + match ( + enable_gpu_occlusion_culling, + gpu_occlusion_culling.is_some(), + ) { + (false, false) | (true, true) => {} + (false, true) => { + *gpu_occlusion_culling = None; + } + (true, false) => { + *gpu_occlusion_culling = Some(GpuOcclusionCullingWorkItemBuffers { + late_indexed: UninitBufferVec::new(BufferUsages::STORAGE), + late_non_indexed: UninitBufferVec::new(BufferUsages::STORAGE), + late_indirect_parameters_indexed_offset: 0, + late_indirect_parameters_non_indexed_offset: 0, + }); + } + } + } + + preprocess_work_item_buffers +} + +/// Initializes work item buffers for a phase in preparation for a new frame. +pub fn init_work_item_buffers( + work_item_buffers: &mut PreprocessWorkItemBuffers, + late_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< + LatePreprocessWorkItemIndirectParameters, + >, + late_non_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec< + LatePreprocessWorkItemIndirectParameters, + >, +) { + // Add the offsets for indirect parameters that the late phase of mesh + // preprocessing writes to. + if let PreprocessWorkItemBuffers::Indirect { + gpu_occlusion_culling: + Some(GpuOcclusionCullingWorkItemBuffers { + ref mut late_indirect_parameters_indexed_offset, + ref mut late_indirect_parameters_non_indexed_offset, + .. + }), + .. + } = *work_item_buffers + { + *late_indirect_parameters_indexed_offset = late_indexed_indirect_parameters_buffer + .push(LatePreprocessWorkItemIndirectParameters::default()) + as u32; + *late_indirect_parameters_non_indexed_offset = late_non_indexed_indirect_parameters_buffer + .push(LatePreprocessWorkItemIndirectParameters::default()) + as u32; } } @@ -493,6 +591,30 @@ impl PreprocessWorkItemBuffers { } } } + + /// Clears out the GPU work item buffers in preparation for a new frame. + pub fn clear(&mut self) { + match *self { + PreprocessWorkItemBuffers::Direct(ref mut buffer) => { + buffer.clear(); + } + PreprocessWorkItemBuffers::Indirect { + indexed: ref mut indexed_buffer, + non_indexed: ref mut non_indexed_buffer, + ref mut gpu_occlusion_culling, + } => { + indexed_buffer.clear(); + non_indexed_buffer.clear(); + + if let Some(ref mut gpu_occlusion_culling) = *gpu_occlusion_culling { + gpu_occlusion_culling.late_indexed.clear(); + gpu_occlusion_culling.late_non_indexed.clear(); + gpu_occlusion_culling.late_indirect_parameters_indexed_offset = 0; + gpu_occlusion_culling.late_indirect_parameters_non_indexed_offset = 0; + } + } + } + } } /// One invocation of the preprocessing shader: i.e. one mesh instance in a @@ -503,14 +625,15 @@ pub struct PreprocessWorkItem { /// The index of the batch input data in the input buffer that the shader /// reads from. pub input_index: u32, - /// The index of the `MeshUniform` in the output buffer that we write to. - /// In direct mode, this is the index of the uniform. In indirect mode, this - /// is the first index uniform in the batch set. - pub output_index: u32, - /// The index of the [`IndirectParametersMetadata`] in the + + /// In direct mode, the index of the mesh uniform; in indirect mode, the + /// index of the [`IndirectParametersGpuMetadata`]. + /// + /// In indirect mode, this is the index of the + /// [`IndirectParametersGpuMetadata`] in the /// `IndirectParametersBuffers::indexed_metadata` or /// `IndirectParametersBuffers::non_indexed_metadata`. - pub indirect_parameters_index: u32, + pub output_or_indirect_parameters_index: u32, } /// The `wgpu` indirect parameters structure that specifies a GPU draw command. @@ -549,23 +672,13 @@ pub struct IndirectParametersNonIndexed { pub first_instance: u32, } -/// A structure, shared between CPU and GPU, that records how many instances of -/// each mesh are actually to be drawn. -/// -/// The CPU writes to this structure in order to initialize the fields other -/// than [`Self::early_instance_count`] and [`Self::late_instance_count`]. The -/// GPU mesh preprocessing shader increments the [`Self::early_instance_count`] -/// and [`Self::late_instance_count`] as it determines that meshes are visible. -/// The indirect parameter building shader reads this metadata in order to -/// construct the indirect draw parameters. +/// A structure, initialized on CPU and read on GPU, that contains metadata +/// about each batch. /// /// Each batch will have one instance of this structure. #[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] #[repr(C)] -pub struct IndirectParametersMetadata { - /// The index of the mesh in the array of `MeshInputUniform`s. - pub mesh_index: u32, - +pub struct IndirectParametersCpuMetadata { /// The index of the first instance of this mesh in the array of /// `MeshUniform`s. /// @@ -580,9 +693,26 @@ pub struct IndirectParametersMetadata { /// /// A *batch set* is a set of meshes that may be multi-drawn together. /// Multiple batches (and therefore multiple instances of - /// [`IndirectParametersMetadata`] structures) can be part of the same batch - /// set. + /// [`IndirectParametersGpuMetadata`] structures) can be part of the same + /// batch set. pub batch_set_index: u32, +} + +/// A structure, written and read GPU, that records how many instances of each +/// mesh are actually to be drawn. +/// +/// The GPU mesh preprocessing shader increments the +/// [`Self::early_instance_count`] and [`Self::late_instance_count`] as it +/// determines that meshes are visible. The indirect parameter building shader +/// reads this metadata in order to construct the indirect draw parameters. +/// +/// Each batch will have one instance of this structure. +#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct IndirectParametersGpuMetadata { + /// The index of the first mesh in this batch in the array of + /// `MeshInputUniform`s. + pub mesh_index: u32, /// The number of instances that were judged visible last frame. /// @@ -634,77 +764,233 @@ pub struct IndirectBatchSet { /// (`multi_draw_indirect`, `multi_draw_indirect_count`) use to draw the scene. /// /// In addition to the indirect draw buffers themselves, this structure contains -/// the buffers that store [`IndirectParametersMetadata`], which are the +/// the buffers that store [`IndirectParametersGpuMetadata`], which are the /// structures that culling writes to so that the indirect parameter building /// pass can determine how many meshes are actually to be drawn. /// /// These buffers will remain empty if indirect drawing isn't in use. -#[derive(Resource)] +#[derive(Resource, Deref, DerefMut)] pub struct IndirectParametersBuffers { - /// The GPU buffer that stores the indirect draw parameters for non-indexed - /// meshes. + /// A mapping from a phase type ID to the indirect parameters buffers for + /// that phase. /// - /// The indirect parameters building shader writes to this buffer, while the - /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from - /// it to perform the draws. - non_indexed_data: UninitBufferVec, + /// Examples of phase type IDs are `Opaque3d` and `AlphaMask3d`. + #[deref] + pub buffers: TypeIdMap, + /// If true, this sets the `COPY_SRC` flag on indirect draw parameters so + /// that they can be read back to CPU. + /// + /// This is a debugging feature that may reduce performance. It primarily + /// exists for the `occlusion_culling` example. + pub allow_copies_from_indirect_parameter_buffers: bool, +} - /// The GPU buffer that holds the data used to construct indirect draw - /// parameters for non-indexed meshes. +impl IndirectParametersBuffers { + /// Initializes a new [`IndirectParametersBuffers`] resource. + pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> IndirectParametersBuffers { + IndirectParametersBuffers { + buffers: TypeIdMap::default(), + allow_copies_from_indirect_parameter_buffers, + } + } +} + +/// The buffers containing all the information that indirect draw commands use +/// to draw the scene, for a single phase. +/// +/// This is the version of the structure that has a type parameter, so that the +/// batching for different phases can run in parallel. +/// +/// See the [`IndirectParametersBuffers`] documentation for more information. +#[derive(Resource)] +pub struct PhaseIndirectParametersBuffers +where + PI: PhaseItem, +{ + /// The indirect draw buffers for the phase. + pub buffers: UntypedPhaseIndirectParametersBuffers, + phantom: PhantomData, +} + +impl PhaseIndirectParametersBuffers +where + PI: PhaseItem, +{ + pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> Self { + PhaseIndirectParametersBuffers { + buffers: UntypedPhaseIndirectParametersBuffers::new( + allow_copies_from_indirect_parameter_buffers, + ), + phantom: PhantomData, + } + } +} + +/// The buffers containing all the information that indirect draw commands use +/// to draw the scene, for a single phase. +/// +/// This is the version of the structure that doesn't have a type parameter, so +/// that it can be inserted into [`IndirectParametersBuffers::buffers`] +/// +/// See the [`IndirectParametersBuffers`] documentation for more information. +pub struct UntypedPhaseIndirectParametersBuffers { + /// Information that indirect draw commands use to draw indexed meshes in + /// the scene. + pub indexed: MeshClassIndirectParametersBuffers, + /// Information that indirect draw commands use to draw non-indexed meshes + /// in the scene. + pub non_indexed: MeshClassIndirectParametersBuffers, +} + +impl UntypedPhaseIndirectParametersBuffers { + /// Creates the indirect parameters buffers. + pub fn new( + allow_copies_from_indirect_parameter_buffers: bool, + ) -> UntypedPhaseIndirectParametersBuffers { + let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT; + if allow_copies_from_indirect_parameter_buffers { + indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC; + } + + UntypedPhaseIndirectParametersBuffers { + non_indexed: MeshClassIndirectParametersBuffers::new( + allow_copies_from_indirect_parameter_buffers, + ), + indexed: MeshClassIndirectParametersBuffers::new( + allow_copies_from_indirect_parameter_buffers, + ), + } + } + + /// Reserves space for `count` new batches. /// - /// The GPU mesh preprocessing shader writes to this buffer, and the - /// indirect parameters building shader reads this buffer to construct the - /// indirect draw parameters. - non_indexed_metadata: RawBufferVec, + /// The `indexed` parameter specifies whether the meshes that these batches + /// correspond to are indexed or not. + pub fn allocate(&mut self, indexed: bool, count: u32) -> u32 { + if indexed { + self.indexed.allocate(count) + } else { + self.non_indexed.allocate(count) + } + } - /// The GPU buffer that holds the number of indirect draw commands for each - /// phase of each view, for non-indexed meshes. + /// Returns the number of batches currently allocated. /// - /// The indirect parameters building shader writes to this buffer, and the - /// `multi_draw_indirect_count` command reads from it in order to know how - /// many indirect draw commands to process. - non_indexed_batch_sets: RawBufferVec, + /// The `indexed` parameter specifies whether the meshes that these batches + /// correspond to are indexed or not. + fn batch_count(&self, indexed: bool) -> usize { + if indexed { + self.indexed.batch_count() + } else { + self.non_indexed.batch_count() + } + } + + /// Returns the number of batch sets currently allocated. + /// + /// The `indexed` parameter specifies whether the meshes that these batch + /// sets correspond to are indexed or not. + pub fn batch_set_count(&self, indexed: bool) -> usize { + if indexed { + self.indexed.batch_sets.len() + } else { + self.non_indexed.batch_sets.len() + } + } + + /// Adds a new batch set to `Self::indexed_batch_sets` or + /// `Self::non_indexed_batch_sets` as appropriate. + /// + /// `indexed` specifies whether the meshes that these batch sets correspond + /// to are indexed or not. `indirect_parameters_base` specifies the offset + /// within `Self::indexed_data` or `Self::non_indexed_data` of the first + /// batch in this batch set. + #[inline] + pub fn add_batch_set(&mut self, indexed: bool, indirect_parameters_base: u32) { + if indexed { + self.indexed.batch_sets.push(IndirectBatchSet { + indirect_parameters_base, + indirect_parameters_count: 0, + }); + } else { + self.non_indexed.batch_sets.push(IndirectBatchSet { + indirect_parameters_base, + indirect_parameters_count: 0, + }); + } + } + + /// Returns the index that a newly-added batch set will have. + /// + /// The `indexed` parameter specifies whether the meshes in such a batch set + /// are indexed or not. + pub fn get_next_batch_set_index(&self, indexed: bool) -> Option { + NonMaxU32::new(self.batch_set_count(indexed) as u32) + } + + /// Clears out the buffers in preparation for a new frame. + pub fn clear(&mut self) { + self.indexed.clear(); + self.non_indexed.clear(); + } +} - /// The GPU buffer that stores the indirect draw parameters for indexed - /// meshes. +/// The buffers containing all the information that indirect draw commands use +/// to draw the scene, for a single mesh class (indexed or non-indexed), for a +/// single phase. +pub struct MeshClassIndirectParametersBuffers +where + IP: Clone + ShaderSize + WriteInto, +{ + /// The GPU buffer that stores the indirect draw parameters for the meshes. /// /// The indirect parameters building shader writes to this buffer, while the /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from /// it to perform the draws. - indexed_data: UninitBufferVec, + data: UninitBufferVec, /// The GPU buffer that holds the data used to construct indirect draw - /// parameters for indexed meshes. + /// parameters for meshes. + /// + /// The GPU mesh preprocessing shader writes to this buffer, and the + /// indirect parameters building shader reads this buffer to construct the + /// indirect draw parameters. + cpu_metadata: RawBufferVec, + + /// The GPU buffer that holds data built by the GPU used to construct + /// indirect draw parameters for meshes. /// /// The GPU mesh preprocessing shader writes to this buffer, and the /// indirect parameters building shader reads this buffer to construct the /// indirect draw parameters. - indexed_metadata: RawBufferVec, + gpu_metadata: UninitBufferVec, /// The GPU buffer that holds the number of indirect draw commands for each - /// phase of each view, for indexed meshes. + /// phase of each view, for meshes. /// /// The indirect parameters building shader writes to this buffer, and the /// `multi_draw_indirect_count` command reads from it in order to know how /// many indirect draw commands to process. - indexed_batch_sets: RawBufferVec, + batch_sets: RawBufferVec, } -impl IndirectParametersBuffers { - /// Creates the indirect parameters buffers. - pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> IndirectParametersBuffers { +impl MeshClassIndirectParametersBuffers +where + IP: Clone + ShaderSize + WriteInto, +{ + fn new( + allow_copies_from_indirect_parameter_buffers: bool, + ) -> MeshClassIndirectParametersBuffers { let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT; if allow_copies_from_indirect_parameter_buffers { indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC; } - IndirectParametersBuffers { - non_indexed_data: UninitBufferVec::new(indirect_parameter_buffer_usages), - non_indexed_metadata: RawBufferVec::new(BufferUsages::STORAGE), - non_indexed_batch_sets: RawBufferVec::new(indirect_parameter_buffer_usages), - indexed_data: UninitBufferVec::new(indirect_parameter_buffer_usages), - indexed_metadata: RawBufferVec::new(BufferUsages::STORAGE), - indexed_batch_sets: RawBufferVec::new(indirect_parameter_buffer_usages), + MeshClassIndirectParametersBuffers { + data: UninitBufferVec::new(indirect_parameter_buffer_usages), + cpu_metadata: RawBufferVec::new(BufferUsages::STORAGE), + gpu_metadata: UninitBufferVec::new(BufferUsages::STORAGE), + batch_sets: RawBufferVec::new(indirect_parameter_buffer_usages), } } @@ -715,181 +1001,77 @@ impl IndirectParametersBuffers { /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from /// it to perform the draws. #[inline] - pub fn indexed_data_buffer(&self) -> Option<&Buffer> { - self.indexed_data.buffer() - } - - /// Returns the GPU buffer that holds the data used to construct indirect - /// draw parameters for indexed meshes. - /// - /// The GPU mesh preprocessing shader writes to this buffer, and the - /// indirect parameters building shader reads this buffer to construct the - /// indirect draw parameters. - #[inline] - pub fn indexed_metadata_buffer(&self) -> Option<&Buffer> { - self.indexed_metadata.buffer() + pub fn data_buffer(&self) -> Option<&Buffer> { + self.data.buffer() } - /// Returns the GPU buffer that holds the number of indirect draw commands - /// for each phase of each view, for indexed meshes. + /// Returns the GPU buffer that holds the CPU-constructed data used to + /// construct indirect draw parameters for meshes. /// - /// The indirect parameters building shader writes to this buffer, and the - /// `multi_draw_indirect_count` command reads from it in order to know how - /// many indirect draw commands to process. + /// The CPU writes to this buffer, and the indirect parameters building + /// shader reads this buffer to construct the indirect draw parameters. #[inline] - pub fn indexed_batch_sets_buffer(&self) -> Option<&Buffer> { - self.indexed_batch_sets.buffer() + pub fn cpu_metadata_buffer(&self) -> Option<&Buffer> { + self.cpu_metadata.buffer() } - /// Returns the GPU buffer that stores the indirect draw parameters for - /// non-indexed meshes. - /// - /// The indirect parameters building shader writes to this buffer, while the - /// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from - /// it to perform the draws. - #[inline] - pub fn non_indexed_data_buffer(&self) -> Option<&Buffer> { - self.non_indexed_data.buffer() - } - - /// Returns the GPU buffer that holds the data used to construct indirect - /// draw parameters for non-indexed meshes. + /// Returns the GPU buffer that holds the GPU-constructed data used to + /// construct indirect draw parameters for meshes. /// /// The GPU mesh preprocessing shader writes to this buffer, and the /// indirect parameters building shader reads this buffer to construct the /// indirect draw parameters. #[inline] - pub fn non_indexed_metadata_buffer(&self) -> Option<&Buffer> { - self.non_indexed_metadata.buffer() + pub fn gpu_metadata_buffer(&self) -> Option<&Buffer> { + self.gpu_metadata.buffer() } /// Returns the GPU buffer that holds the number of indirect draw commands - /// for each phase of each view, for non-indexed meshes. + /// for each phase of each view. /// /// The indirect parameters building shader writes to this buffer, and the /// `multi_draw_indirect_count` command reads from it in order to know how /// many indirect draw commands to process. #[inline] - pub fn non_indexed_batch_sets_buffer(&self) -> Option<&Buffer> { - self.non_indexed_batch_sets.buffer() + pub fn batch_sets_buffer(&self) -> Option<&Buffer> { + self.batch_sets.buffer() } - /// Reserves space for `count` new batches corresponding to indexed meshes. - /// - /// This allocates in both the [`Self::indexed_metadata`] and - /// [`Self::indexed_data`] buffers. - fn allocate_indexed(&mut self, count: u32) -> u32 { - let length = self.indexed_data.len(); - self.indexed_metadata.reserve_internal(count as usize); - for _ in 0..count { - self.indexed_data.add(); - self.indexed_metadata - .push(IndirectParametersMetadata::default()); - } - length as u32 - } - - /// Reserves space for `count` new batches corresponding to non-indexed - /// meshes. + /// Reserves space for `count` new batches. /// - /// This allocates in both the `non_indexed_metadata` and `non_indexed_data` - /// buffers. - pub fn allocate_non_indexed(&mut self, count: u32) -> u32 { - let length = self.non_indexed_data.len(); - self.non_indexed_metadata.reserve_internal(count as usize); + /// This allocates in the [`Self::cpu_metadata`], [`Self::gpu_metadata`], + /// and [`Self::data`] buffers. + fn allocate(&mut self, count: u32) -> u32 { + let length = self.data.len(); + self.cpu_metadata.reserve_internal(count as usize); + self.gpu_metadata.add_multiple(count as usize); for _ in 0..count { - self.non_indexed_data.add(); - self.non_indexed_metadata - .push(IndirectParametersMetadata::default()); + self.data.add(); + self.cpu_metadata + .push(IndirectParametersCpuMetadata::default()); } length as u32 } - /// Reserves space for `count` new batches. - /// - /// The `indexed` parameter specifies whether the meshes that these batches - /// correspond to are indexed or not. - pub fn allocate(&mut self, indexed: bool, count: u32) -> u32 { - if indexed { - self.allocate_indexed(count) - } else { - self.allocate_non_indexed(count) - } - } - - /// Initializes the batch corresponding to an indexed mesh at the given - /// index with the given [`IndirectParametersMetadata`]. - pub fn set_indexed(&mut self, index: u32, value: IndirectParametersMetadata) { - self.indexed_metadata.set(index, value); - } - - /// Initializes the batch corresponding to a non-indexed mesh at the given - /// index with the given [`IndirectParametersMetadata`]. - pub fn set_non_indexed(&mut self, index: u32, value: IndirectParametersMetadata) { - self.non_indexed_metadata.set(index, value); - } - - /// Returns the number of batches currently allocated. - /// - /// The `indexed` parameter specifies whether the meshes that these batches - /// correspond to are indexed or not. - fn batch_count(&self, indexed: bool) -> usize { - if indexed { - self.indexed_batch_count() - } else { - self.non_indexed_batch_count() - } - } - - /// Returns the number of batches corresponding to indexed meshes that are - /// currently allocated. - #[inline] - pub fn indexed_batch_count(&self) -> usize { - self.indexed_data.len() + /// Sets the [`IndirectParametersCpuMetadata`] for the mesh at the given + /// index. + pub fn set(&mut self, index: u32, value: IndirectParametersCpuMetadata) { + self.cpu_metadata.set(index, value); } - /// Returns the number of batches corresponding to non-indexed meshes that - /// are currently allocated. + /// Returns the number of batches corresponding to meshes that are currently + /// allocated. #[inline] - pub fn non_indexed_batch_count(&self) -> usize { - self.non_indexed_data.len() + pub fn batch_count(&self) -> usize { + self.data.len() } - /// Returns the number of batch sets currently allocated. - /// - /// The `indexed` parameter specifies whether the meshes that these batch - /// sets correspond to are indexed or not. - pub fn batch_set_count(&self, indexed: bool) -> usize { - if indexed { - self.indexed_batch_sets.len() - } else { - self.non_indexed_batch_sets.len() - } - } - - /// Adds a new batch set to `Self::indexed_batch_sets` or - /// `Self::non_indexed_batch_sets` as appropriate. - /// - /// `indexed` specifies whether the meshes that these batch sets correspond - /// to are indexed or not. `indirect_parameters_base` specifies the offset - /// within `Self::indexed_data` or `Self::non_indexed_data` of the first - /// batch in this batch set. - pub fn add_batch_set(&mut self, indexed: bool, indirect_parameters_base: u32) { - if indexed { - self.indexed_batch_sets.push(IndirectBatchSet { - indirect_parameters_base, - indirect_parameters_count: 0, - }); - } else { - self.non_indexed_batch_sets.push(IndirectBatchSet { - indirect_parameters_base, - indirect_parameters_count: 0, - }); - } - } - - pub fn get_next_batch_set_index(&self, indexed: bool) -> Option { - NonMaxU32::new(self.batch_set_count(indexed) as u32) + /// Clears out all the buffers in preparation for a new frame. + pub fn clear(&mut self) { + self.data.clear(); + self.cpu_metadata.clear(); + self.gpu_metadata.clear(); + self.batch_sets.clear(); } } @@ -914,16 +1096,22 @@ impl FromWorld for GpuPreprocessingSupport { crate::get_adreno_model(adapter).is_some_and(|model| model != 720 && model <= 730) } - let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 0 || - is_non_supported_android_device(adapter) + let 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 downlevel_support = adapter.get_downlevel_capabilities().flags.contains( + 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) { GpuPreprocessingMode::None - } else if !device - .features() - .contains(Features::INDIRECT_FIRST_INSTANCE | Features::MULTI_DRAW_INDIRECT) || - !adapter.get_downlevel_capabilities().flags.contains( - DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW) - { + } else if !(feature_support && limit_support && downlevel_support) { GpuPreprocessingMode::PreprocessingOnly } else { GpuPreprocessingMode::Culling @@ -940,11 +1128,25 @@ where { /// Creates new buffers. pub fn new() -> Self { - BatchedInstanceBuffers { + Self::default() + } + + /// Clears out the buffers in preparation for a new frame. + pub fn clear(&mut self) { + for phase_instance_buffer in self.phase_instance_buffers.values_mut() { + phase_instance_buffer.clear(); + } + } +} + +impl UntypedPhaseBatchedInstanceBuffers +where + BD: GpuArrayBufferable + Sync + Send + 'static, +{ + pub fn new() -> Self { + UntypedPhaseBatchedInstanceBuffers { data_buffer: UninitBufferVec::new(BufferUsages::STORAGE), - work_item_buffers: EntityHashMap::default(), - current_input_buffer: InstanceInputUniformBuffer::new(), - previous_input_buffer: InstanceInputUniformBuffer::new(), + work_item_buffers: HashMap::default(), late_indexed_indirect_parameters_buffer: RawBufferVec::new( BufferUsages::STORAGE | BufferUsages::INDIRECT, ), @@ -968,14 +1170,18 @@ where self.data_buffer.clear(); self.late_indexed_indirect_parameters_buffer.clear(); self.late_non_indexed_indirect_parameters_buffer.clear(); - self.work_item_buffers.clear(); + + // Clear each individual set of buffers, but don't depopulate the hash + // table. We want to avoid reallocating these vectors every frame. + for view_work_item_buffers in self.work_item_buffers.values_mut() { + view_work_item_buffers.clear(); + } } } -impl Default for BatchedInstanceBuffers +impl Default for UntypedPhaseBatchedInstanceBuffers where BD: GpuArrayBufferable + Sync + Send + 'static, - BDI: Pod + Default + Sync + Send + 'static, { fn default() -> Self { Self::new() @@ -1024,7 +1230,7 @@ where self, instance_end_index: u32, phase: &mut SortedRenderPhase, - indirect_parameters_buffers: &mut IndirectParametersBuffers, + phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, ) where I: CachedRenderPipelinePhaseItem + SortedPhaseItem, { @@ -1040,7 +1246,7 @@ where None => PhaseItemExtraIndex::None, }; if let Some(indirect_parameters_index) = self.indirect_parameters_index { - indirect_parameters_buffers + phase_indirect_parameters_buffers .add_batch_set(self.indexed, indirect_parameters_index.into()); } } @@ -1059,6 +1265,8 @@ pub fn clear_batched_gpu_instance_buffers( ) where GFBD: GetFullBatchData, { + // Don't clear the entire table, because that would delete the buffers, and + // we want to reuse those allocations. if let Some(mut gpu_batched_instance_buffers) = gpu_batched_instance_buffers { gpu_batched_instance_buffers.clear(); } @@ -1074,24 +1282,34 @@ pub fn delete_old_work_item_buffers( mut gpu_batched_instance_buffers: ResMut< BatchedInstanceBuffers, >, - extracted_views: Query>, + extracted_views: Query<&ExtractedView>, ) where GFBD: GetFullBatchData, { - gpu_batched_instance_buffers - .work_item_buffers - .retain(|entity, _| extracted_views.contains(*entity)); + let retained_view_entities: HashSet<_> = extracted_views + .iter() + .map(|extracted_view| extracted_view.retained_view_entity) + .collect(); + for phase_instance_buffers in gpu_batched_instance_buffers + .phase_instance_buffers + .values_mut() + { + phase_instance_buffers + .work_item_buffers + .retain(|retained_view_entity, _| { + retained_view_entities.contains(retained_view_entity) + }); + } } /// Batch the items in a sorted render phase, when GPU instance buffer building /// is in use. This means comparing metadata needed to draw each phase item and /// trying to combine the draws into a batch. pub fn batch_and_prepare_sorted_render_phase( - gpu_array_buffer: ResMut>, - mut indirect_parameters_buffers: ResMut, + mut phase_batched_instance_buffers: ResMut>, + mut phase_indirect_parameters_buffers: ResMut>, mut sorted_render_phases: ResMut>, mut views: Query<( - Entity, &ExtractedView, Has, Has, @@ -1102,15 +1320,14 @@ pub fn batch_and_prepare_sorted_render_phase( GFBD: GetFullBatchData, { // We only process GPU-built batch data in this function. - let BatchedInstanceBuffers { + let UntypedPhaseBatchedInstanceBuffers { ref mut data_buffer, ref mut work_item_buffers, ref mut late_indexed_indirect_parameters_buffer, ref mut late_non_indexed_indirect_parameters_buffer, - .. - } = gpu_array_buffer.into_inner(); + } = phase_batched_instance_buffers.buffers; - for (view, extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { + for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { let Some(phase) = sorted_render_phases.get_mut(&extracted_view.retained_view_entity) else { continue; }; @@ -1118,9 +1335,14 @@ pub fn batch_and_prepare_sorted_render_phase( // Create the work item buffer if necessary. let work_item_buffer = get_or_create_work_item_buffer::( work_item_buffers, - view, + extracted_view.retained_view_entity, no_indirect_drawing, gpu_occlusion_culling, + ); + + // Initialize those work item buffers in preparation for this new frame. + init_work_item_buffers( + work_item_buffer, late_indexed_indirect_parameters_buffer, late_non_indexed_indirect_parameters_buffer, ); @@ -1128,8 +1350,6 @@ pub fn batch_and_prepare_sorted_render_phase( // Walk through the list of phase items, building up batches as we go. let mut batch: Option> = None; - let mut first_output_index = data_buffer.len() as u32; - for current_index in 0..phase.items.len() { // Get the index of the input data, and comparison metadata, for // this entity. @@ -1149,7 +1369,7 @@ pub fn batch_and_prepare_sorted_render_phase( batch.flush( data_buffer.len() as u32, phase, - &mut indirect_parameters_buffers, + &mut phase_indirect_parameters_buffers.buffers, ); } @@ -1175,25 +1395,38 @@ pub fn batch_and_prepare_sorted_render_phase( if !can_batch { // Break a batch if we need to. if let Some(batch) = batch.take() { - batch.flush(output_index, phase, &mut indirect_parameters_buffers); + batch.flush( + output_index, + phase, + &mut phase_indirect_parameters_buffers.buffers, + ); } let indirect_parameters_index = if no_indirect_drawing { None } else if item_is_indexed { - Some(indirect_parameters_buffers.allocate_indexed(1)) + Some( + phase_indirect_parameters_buffers + .buffers + .indexed + .allocate(1), + ) } else { - Some(indirect_parameters_buffers.allocate_non_indexed(1)) + Some( + phase_indirect_parameters_buffers + .buffers + .non_indexed + .allocate(1), + ) }; // Start a new batch. if let Some(indirect_parameters_index) = indirect_parameters_index { GFBD::write_batch_indirect_parameters_metadata( - current_input_index.into(), item_is_indexed, output_index, None, - &mut indirect_parameters_buffers, + &mut phase_indirect_parameters_buffers.buffers, indirect_parameters_index, ); }; @@ -1205,8 +1438,6 @@ pub fn batch_and_prepare_sorted_render_phase( indirect_parameters_index: indirect_parameters_index.and_then(NonMaxU32::new), meta: current_meta, }); - - first_output_index = output_index; } // Add a new preprocessing work item so that the preprocessing @@ -1216,14 +1447,15 @@ pub fn batch_and_prepare_sorted_render_phase( item_is_indexed, PreprocessWorkItem { input_index: current_input_index.into(), - output_index: if no_indirect_drawing { - output_index - } else { - first_output_index - }, - indirect_parameters_index: match batch.indirect_parameters_index { - Some(indirect_parameters_index) => indirect_parameters_index.into(), - None => 0, + output_or_indirect_parameters_index: match ( + no_indirect_drawing, + batch.indirect_parameters_index, + ) { + (true, _) => output_index, + (false, Some(indirect_parameters_index)) => { + indirect_parameters_index.into() + } + (false, None) => 0, }, }, ); @@ -1235,7 +1467,7 @@ pub fn batch_and_prepare_sorted_render_phase( batch.flush( data_buffer.len() as u32, phase, - &mut indirect_parameters_buffers, + &mut phase_indirect_parameters_buffers.buffers, ); } } @@ -1243,12 +1475,11 @@ pub fn batch_and_prepare_sorted_render_phase( /// Creates batches for a render phase that uses bins. pub fn batch_and_prepare_binned_render_phase( - gpu_array_buffer: ResMut>, - mut indirect_parameters_buffers: ResMut, + mut phase_batched_instance_buffers: ResMut>, + phase_indirect_parameters_buffers: ResMut>, mut binned_render_phases: ResMut>, mut views: Query< ( - Entity, &ExtractedView, Has, Has, @@ -1262,15 +1493,16 @@ pub fn batch_and_prepare_binned_render_phase( { let system_param_item = param.into_inner(); - let BatchedInstanceBuffers { + let phase_indirect_parameters_buffers = phase_indirect_parameters_buffers.into_inner(); + + let UntypedPhaseBatchedInstanceBuffers { ref mut data_buffer, ref mut work_item_buffers, ref mut late_indexed_indirect_parameters_buffer, ref mut late_non_indexed_indirect_parameters_buffer, - .. - } = gpu_array_buffer.into_inner(); + } = phase_batched_instance_buffers.buffers; - for (view, extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { + for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views { let Some(phase) = binned_render_phases.get_mut(&extracted_view.retained_view_entity) else { continue; }; @@ -1279,126 +1511,90 @@ pub fn batch_and_prepare_binned_render_phase( // used this frame. let work_item_buffer = get_or_create_work_item_buffer::( work_item_buffers, - view, + extracted_view.retained_view_entity, no_indirect_drawing, gpu_occlusion_culling, + ); + + // Initialize those work item buffers in preparation for this new frame. + init_work_item_buffers( + work_item_buffer, late_indexed_indirect_parameters_buffer, late_non_indexed_indirect_parameters_buffer, ); // Prepare multidrawables. - for batch_set_key in &phase.multidrawable_mesh_keys { - let mut batch_set = None; - let indirect_parameters_base = - indirect_parameters_buffers.batch_count(batch_set_key.indexed()) as u32; - for (bin_key, bin) in &phase.multidrawable_mesh_values[batch_set_key] { - let first_output_index = data_buffer.len() as u32; - let mut batch: Option = None; - - for &(entity, main_entity) in &bin.entities { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, main_entity) - else { - continue; - }; - let output_index = data_buffer.add() as u32; - - match batch { - Some(ref mut batch) => { - // Append to the current batch. - batch.instance_range.end = output_index + 1; - work_item_buffer.push( - batch_set_key.indexed(), - PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, - indirect_parameters_index: match batch.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex { - ref range, - .. - } => range.start, - PhaseItemExtraIndex::DynamicOffset(_) - | PhaseItemExtraIndex::None => 0, - }, - }, - ); - } - - None => { - // Start a new batch, in indirect mode. - let indirect_parameters_index = - indirect_parameters_buffers.allocate(batch_set_key.indexed(), 1); - let batch_set_index = indirect_parameters_buffers - .get_next_batch_set_index(batch_set_key.indexed()); - - GFBD::write_batch_indirect_parameters_metadata( - input_index.into(), - batch_set_key.indexed(), - output_index, - batch_set_index, - &mut indirect_parameters_buffers, - indirect_parameters_index, - ); - work_item_buffer.push( - batch_set_key.indexed(), - PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, - indirect_parameters_index, - }, - ); - batch = Some(BinnedRenderPhaseBatch { - representative_entity: (entity, main_entity), - instance_range: output_index..output_index + 1, - extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index( - NonMaxU32::new(indirect_parameters_index), - ), - }); - } - } - } + if let ( + &mut BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets), + &mut PreprocessWorkItemBuffers::Indirect { + indexed: ref mut indexed_work_item_buffer, + non_indexed: ref mut non_indexed_work_item_buffer, + gpu_occlusion_culling: ref mut gpu_occlusion_culling_buffers, + }, + ) = (&mut phase.batch_sets, &mut *work_item_buffer) + { + let mut output_index = data_buffer.len() as u32; + + // Initialize the state for both indexed and non-indexed meshes. + let mut indexed_preparer: MultidrawableBatchSetPreparer = + MultidrawableBatchSetPreparer::new( + phase_indirect_parameters_buffers.buffers.batch_count(true) as u32, + phase_indirect_parameters_buffers + .buffers + .indexed + .batch_sets + .len() as u32, + ); + let mut non_indexed_preparer: MultidrawableBatchSetPreparer = + MultidrawableBatchSetPreparer::new( + phase_indirect_parameters_buffers.buffers.batch_count(false) as u32, + phase_indirect_parameters_buffers + .buffers + .non_indexed + .batch_sets + .len() as u32, + ); - if let Some(batch) = batch { - match batch_set { - None => { - batch_set = Some(BinnedRenderPhaseBatchSet { - first_batch: batch, - batch_count: 1, - bin_key: bin_key.clone(), - index: indirect_parameters_buffers - .batch_set_count(batch_set_key.indexed()) - as u32, - }); - } - Some(ref mut batch_set) => { - batch_set.batch_count += 1; - } - } + // Prepare each batch set. + for (batch_set_key, bins) in &phase.multidrawable_meshes { + if batch_set_key.indexed() { + indexed_preparer.prepare_multidrawable_binned_batch_set( + bins, + &mut output_index, + data_buffer, + indexed_work_item_buffer, + &mut phase_indirect_parameters_buffers.buffers.indexed, + batch_sets, + ); + } else { + non_indexed_preparer.prepare_multidrawable_binned_batch_set( + bins, + &mut output_index, + data_buffer, + non_indexed_work_item_buffer, + &mut phase_indirect_parameters_buffers.buffers.non_indexed, + batch_sets, + ); } } - if let BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets) = - phase.batch_sets - { - if let Some(batch_set) = batch_set { - batch_sets.push(batch_set); - indirect_parameters_buffers - .add_batch_set(batch_set_key.indexed(), indirect_parameters_base); - } + // Reserve space in the occlusion culling buffers, if necessary. + if let Some(ref mut gpu_occlusion_culling_buffers) = gpu_occlusion_culling_buffers { + gpu_occlusion_culling_buffers + .late_indexed + .add_multiple(indexed_preparer.work_item_count); + gpu_occlusion_culling_buffers + .late_non_indexed + .add_multiple(non_indexed_preparer.work_item_count); } } // Prepare batchables. - for key in &phase.batchable_mesh_keys { - let first_output_index = data_buffer.len() as u32; - + for (key, bin) in &phase.batchable_meshes { let mut batch: Option = None; - for &(entity, main_entity) in &phase.batchable_mesh_values[key].entities { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, main_entity) - else { - continue; - }; + for (&main_entity, &input_index) in bin.entities() { let output_index = data_buffer.add() as u32; match batch { @@ -1415,19 +1611,21 @@ pub fn batch_and_prepare_binned_render_phase( work_item_buffer.push( key.0.indexed(), PreprocessWorkItem { - input_index: input_index.into(), - output_index: if no_indirect_drawing { - output_index - } else { - first_output_index - }, - indirect_parameters_index: match batch.extra_index { - PhaseItemExtraIndex::IndirectParametersIndex { - range: ref indirect_parameters_range, - .. - } => indirect_parameters_range.start, - PhaseItemExtraIndex::DynamicOffset(_) - | PhaseItemExtraIndex::None => 0, + input_index: *input_index, + output_or_indirect_parameters_index: match ( + no_indirect_drawing, + &batch.extra_index, + ) { + (true, _) => output_index, + ( + false, + PhaseItemExtraIndex::IndirectParametersIndex { + range: indirect_parameters_range, + .. + }, + ) => indirect_parameters_range.start, + (false, &PhaseItemExtraIndex::DynamicOffset(_)) + | (false, &PhaseItemExtraIndex::None) => 0, }, }, ); @@ -1435,29 +1633,29 @@ pub fn batch_and_prepare_binned_render_phase( None if !no_indirect_drawing => { // Start a new batch, in indirect mode. - let indirect_parameters_index = - indirect_parameters_buffers.allocate(key.0.indexed(), 1); - let batch_set_index = - indirect_parameters_buffers.get_next_batch_set_index(key.0.indexed()); + let indirect_parameters_index = phase_indirect_parameters_buffers + .buffers + .allocate(key.0.indexed(), 1); + let batch_set_index = phase_indirect_parameters_buffers + .buffers + .get_next_batch_set_index(key.0.indexed()); GFBD::write_batch_indirect_parameters_metadata( - input_index.into(), key.0.indexed(), output_index, batch_set_index, - &mut indirect_parameters_buffers, + &mut phase_indirect_parameters_buffers.buffers, indirect_parameters_index, ); work_item_buffer.push( key.0.indexed(), PreprocessWorkItem { - input_index: input_index.into(), - output_index: first_output_index, - indirect_parameters_index, + input_index: *input_index, + output_or_indirect_parameters_index: indirect_parameters_index, }, ); batch = Some(BinnedRenderPhaseBatch { - representative_entity: (entity, main_entity), + representative_entity: (Entity::PLACEHOLDER, main_entity), instance_range: output_index..output_index + 1, extra_index: PhaseItemExtraIndex::IndirectParametersIndex { range: indirect_parameters_index..(indirect_parameters_index + 1), @@ -1471,13 +1669,12 @@ pub fn batch_and_prepare_binned_render_phase( work_item_buffer.push( key.0.indexed(), PreprocessWorkItem { - input_index: input_index.into(), - output_index, - indirect_parameters_index: 0, + input_index: *input_index, + output_or_indirect_parameters_index: output_index, }, ); batch = Some(BinnedRenderPhaseBatch { - representative_entity: (entity, main_entity), + representative_entity: (Entity::PLACEHOLDER, main_entity), instance_range: output_index..output_index + 1, extra_index: PhaseItemExtraIndex::None, }); @@ -1502,7 +1699,9 @@ pub fn batch_and_prepare_binned_render_phase( first_batch: batch, batch_count: 1, bin_key: key.1.clone(), - index: indirect_parameters_buffers.batch_set_count(key.0.indexed()) + index: phase_indirect_parameters_buffers + .buffers + .batch_set_count(key.0.indexed()) as u32, }); } @@ -1511,26 +1710,28 @@ pub fn batch_and_prepare_binned_render_phase( } // Prepare unbatchables. - for key in &phase.unbatchable_mesh_keys { - let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap(); - + for (key, unbatchables) in &mut phase.unbatchable_meshes { // Allocate the indirect parameters if necessary. let mut indirect_parameters_offset = if no_indirect_drawing { None } else if key.0.indexed() { Some( - indirect_parameters_buffers - .allocate_indexed(unbatchables.entities.len() as u32), + phase_indirect_parameters_buffers + .buffers + .indexed + .allocate(unbatchables.entities.len() as u32), ) } else { Some( - indirect_parameters_buffers - .allocate_non_indexed(unbatchables.entities.len() as u32), + phase_indirect_parameters_buffers + .buffers + .non_indexed + .allocate(unbatchables.entities.len() as u32), ) }; - for &(_, main_entity) in &unbatchables.entities { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, main_entity) + for main_entity in unbatchables.entities.keys() { + let Some(input_index) = GFBD::get_binned_index(&system_param_item, *main_entity) else { continue; }; @@ -1540,19 +1741,17 @@ pub fn batch_and_prepare_binned_render_phase( // We're in indirect mode, so add an indirect parameters // index. GFBD::write_batch_indirect_parameters_metadata( - input_index.into(), key.0.indexed(), output_index, None, - &mut indirect_parameters_buffers, + &mut phase_indirect_parameters_buffers.buffers, *indirect_parameters_index, ); work_item_buffer.push( key.0.indexed(), PreprocessWorkItem { input_index: input_index.into(), - output_index, - indirect_parameters_index: *indirect_parameters_index, + output_or_indirect_parameters_index: *indirect_parameters_index, }, ); unbatchables @@ -1564,7 +1763,8 @@ pub fn batch_and_prepare_binned_render_phase( batch_set_index: None, }, }); - indirect_parameters_buffers + phase_indirect_parameters_buffers + .buffers .add_batch_set(key.0.indexed(), *indirect_parameters_index); *indirect_parameters_index += 1; } else { @@ -1572,8 +1772,7 @@ pub fn batch_and_prepare_binned_render_phase( key.0.indexed(), PreprocessWorkItem { input_index: input_index.into(), - output_index, - indirect_parameters_index: 0, + output_or_indirect_parameters_index: output_index, }, ); unbatchables @@ -1588,6 +1787,195 @@ pub fn batch_and_prepare_binned_render_phase( } } +/// The state that [`batch_and_prepare_binned_render_phase`] uses to construct +/// multidrawable batch sets. +/// +/// The [`batch_and_prepare_binned_render_phase`] system maintains two of these: +/// one for indexed meshes and one for non-indexed meshes. +struct MultidrawableBatchSetPreparer +where + BPI: BinnedPhaseItem, + GFBD: GetFullBatchData, +{ + /// The offset in the indirect parameters buffer at which the next indirect + /// parameters will be written. + indirect_parameters_index: u32, + /// The number of batch sets we've built so far for this mesh class. + batch_set_index: u32, + /// The number of work items we've emitted so far for this mesh class. + work_item_count: usize, + phantom: PhantomData<(BPI, GFBD)>, +} + +impl MultidrawableBatchSetPreparer +where + BPI: BinnedPhaseItem, + GFBD: GetFullBatchData, +{ + /// Creates a new [`MultidrawableBatchSetPreparer`] that will start writing + /// indirect parameters and batch sets at the given indices. + #[inline] + fn new(initial_indirect_parameters_index: u32, initial_batch_set_index: u32) -> Self { + MultidrawableBatchSetPreparer { + indirect_parameters_index: initial_indirect_parameters_index, + batch_set_index: initial_batch_set_index, + work_item_count: 0, + phantom: PhantomData, + } + } + + /// Creates batch sets and writes the GPU data needed to draw all visible + /// entities of one mesh class in the given batch set. + /// + /// The *mesh class* represents whether the mesh has indices or not. + #[inline] + fn prepare_multidrawable_binned_batch_set( + &mut self, + bins: &IndexMap, + output_index: &mut u32, + data_buffer: &mut UninitBufferVec, + indexed_work_item_buffer: &mut RawBufferVec, + mesh_class_buffers: &mut MeshClassIndirectParametersBuffers, + batch_sets: &mut Vec>, + ) where + IP: Clone + ShaderSize + WriteInto, + { + let current_indexed_batch_set_index = self.batch_set_index; + let current_output_index = *output_index; + + let indirect_parameters_base = self.indirect_parameters_index; + + // We're going to write the first entity into the batch set. Do this + // here so that we can preload the bin into cache as a side effect. + let Some((first_bin_key, first_bin)) = bins.iter().next() else { + return; + }; + let first_bin_len = first_bin.entities().len(); + let first_bin_entity = first_bin + .entities() + .keys() + .next() + .copied() + .unwrap_or(MainEntity::from(Entity::PLACEHOLDER)); + + // Traverse the batch set, processing each bin. + for bin in bins.values() { + // Record the first output index for this batch, as well as its own + // index. + mesh_class_buffers + .cpu_metadata + .push(IndirectParametersCpuMetadata { + base_output_index: *output_index, + batch_set_index: self.batch_set_index, + }); + + // Traverse the bin, pushing `PreprocessWorkItem`s for each entity + // within it. This is a hot loop, so make it as fast as possible. + for &input_index in bin.entities().values() { + indexed_work_item_buffer.push(PreprocessWorkItem { + input_index: *input_index, + output_or_indirect_parameters_index: self.indirect_parameters_index, + }); + } + + // Reserve space for the appropriate number of entities in the data + // buffer. Also, advance the output index and work item count. + let bin_entity_count = bin.entities().len(); + data_buffer.add_multiple(bin_entity_count); + *output_index += bin_entity_count as u32; + self.work_item_count += bin_entity_count; + + self.indirect_parameters_index += 1; + } + + // Reserve space for the bins in this batch set in the GPU buffers. + let bin_count = bins.len(); + mesh_class_buffers.gpu_metadata.add_multiple(bin_count); + mesh_class_buffers.data.add_multiple(bin_count); + + // Write the information the GPU will need about this batch set. + mesh_class_buffers.batch_sets.push(IndirectBatchSet { + indirect_parameters_base, + indirect_parameters_count: 0, + }); + + self.batch_set_index += 1; + + // Record the batch set. The render node later processes this record to + // render the batches. + batch_sets.push(BinnedRenderPhaseBatchSet { + first_batch: BinnedRenderPhaseBatch { + representative_entity: (Entity::PLACEHOLDER, first_bin_entity), + instance_range: current_output_index..(current_output_index + first_bin_len as u32), + extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index(NonMaxU32::new( + indirect_parameters_base, + )), + }, + bin_key: (*first_bin_key).clone(), + batch_count: self.indirect_parameters_index - indirect_parameters_base, + index: current_indexed_batch_set_index, + }); + } +} + +/// A system that gathers up the per-phase GPU buffers and inserts them into the +/// [`BatchedInstanceBuffers`] and [`IndirectParametersBuffers`] tables. +/// +/// This runs after the [`batch_and_prepare_binned_render_phase`] or +/// [`batch_and_prepare_sorted_render_phase`] systems. It takes the per-phase +/// [`PhaseBatchedInstanceBuffers`] and [`PhaseIndirectParametersBuffers`] +/// resources and inserts them into the global [`BatchedInstanceBuffers`] and +/// [`IndirectParametersBuffers`] tables. +/// +/// This system exists so that the [`batch_and_prepare_binned_render_phase`] and +/// [`batch_and_prepare_sorted_render_phase`] can run in parallel with one +/// another. If those two systems manipulated [`BatchedInstanceBuffers`] and +/// [`IndirectParametersBuffers`] directly, then they wouldn't be able to run in +/// parallel. +pub fn collect_buffers_for_phase( + mut phase_batched_instance_buffers: ResMut>, + mut phase_indirect_parameters_buffers: ResMut>, + mut batched_instance_buffers: ResMut< + BatchedInstanceBuffers, + >, + mut indirect_parameters_buffers: ResMut, +) where + PI: PhaseItem, + GFBD: GetFullBatchData + Send + Sync + 'static, +{ + // Insert the `PhaseBatchedInstanceBuffers` into the global table. Replace + // the contents of the per-phase resource with the old batched instance + // buffers in order to reuse allocations. + let untyped_phase_batched_instance_buffers = + mem::take(&mut phase_batched_instance_buffers.buffers); + if let Some(mut old_untyped_phase_batched_instance_buffers) = batched_instance_buffers + .phase_instance_buffers + .insert(TypeId::of::(), untyped_phase_batched_instance_buffers) + { + old_untyped_phase_batched_instance_buffers.clear(); + phase_batched_instance_buffers.buffers = old_untyped_phase_batched_instance_buffers; + } + + // Insert the `PhaseIndirectParametersBuffers` into the global table. + // Replace the contents of the per-phase resource with the old indirect + // parameters buffers in order to reuse allocations. + let untyped_phase_indirect_parameters_buffers = mem::replace( + &mut phase_indirect_parameters_buffers.buffers, + UntypedPhaseIndirectParametersBuffers::new( + indirect_parameters_buffers.allow_copies_from_indirect_parameter_buffers, + ), + ); + if let Some(mut old_untyped_phase_indirect_parameters_buffers) = indirect_parameters_buffers + .insert( + TypeId::of::(), + untyped_phase_indirect_parameters_buffers, + ) + { + old_untyped_phase_indirect_parameters_buffers.clear(); + phase_indirect_parameters_buffers.buffers = old_untyped_phase_indirect_parameters_buffers; + } +} + /// A system that writes all instance buffers to the GPU. pub fn write_batched_instance_buffers( render_device: Res, @@ -1597,26 +1985,31 @@ pub fn write_batched_instance_buffers( GFBD: GetFullBatchData, { let BatchedInstanceBuffers { - ref mut data_buffer, - ref mut work_item_buffers, ref mut current_input_buffer, ref mut previous_input_buffer, - ref mut late_indexed_indirect_parameters_buffer, - ref mut late_non_indexed_indirect_parameters_buffer, + ref mut phase_instance_buffers, } = gpu_array_buffer.into_inner(); - data_buffer.write_buffer(&render_device); current_input_buffer .buffer .write_buffer(&render_device, &render_queue); previous_input_buffer .buffer .write_buffer(&render_device, &render_queue); - late_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue); - late_non_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue); - for view_work_item_buffers in work_item_buffers.values_mut() { - for phase_work_item_buffers in view_work_item_buffers.values_mut() { + for phase_instance_buffers in phase_instance_buffers.values_mut() { + let UntypedPhaseBatchedInstanceBuffers { + ref mut data_buffer, + ref mut work_item_buffers, + ref mut late_indexed_indirect_parameters_buffer, + ref mut late_non_indexed_indirect_parameters_buffer, + } = *phase_instance_buffers; + + data_buffer.write_buffer(&render_device); + late_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue); + late_non_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue); + + for phase_work_item_buffers in work_item_buffers.values_mut() { match *phase_work_item_buffers { PreprocessWorkItemBuffers::Direct(ref mut buffer_vec) => { buffer_vec.write_buffer(&render_device, &render_queue); @@ -1652,12 +2045,9 @@ pub fn write_batched_instance_buffers( pub fn clear_indirect_parameters_buffers( mut indirect_parameters_buffers: ResMut, ) { - indirect_parameters_buffers.indexed_data.clear(); - indirect_parameters_buffers.indexed_metadata.clear(); - indirect_parameters_buffers.indexed_batch_sets.clear(); - indirect_parameters_buffers.non_indexed_data.clear(); - indirect_parameters_buffers.non_indexed_metadata.clear(); - indirect_parameters_buffers.non_indexed_batch_sets.clear(); + for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() { + phase_indirect_parameters_buffers.clear(); + } } pub fn write_indirect_parameters_buffers( @@ -1665,26 +2055,43 @@ pub fn write_indirect_parameters_buffers( render_queue: Res, mut indirect_parameters_buffers: ResMut, ) { - indirect_parameters_buffers - .indexed_data - .write_buffer(&render_device); - indirect_parameters_buffers - .non_indexed_data - .write_buffer(&render_device); - - indirect_parameters_buffers - .indexed_metadata - .write_buffer(&render_device, &render_queue); - indirect_parameters_buffers - .non_indexed_metadata - .write_buffer(&render_device, &render_queue); - - indirect_parameters_buffers - .indexed_batch_sets - .write_buffer(&render_device, &render_queue); - indirect_parameters_buffers - .non_indexed_batch_sets - .write_buffer(&render_device, &render_queue); + for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() { + phase_indirect_parameters_buffers + .indexed + .data + .write_buffer(&render_device); + phase_indirect_parameters_buffers + .non_indexed + .data + .write_buffer(&render_device); + + phase_indirect_parameters_buffers + .indexed + .cpu_metadata + .write_buffer(&render_device, &render_queue); + phase_indirect_parameters_buffers + .non_indexed + .cpu_metadata + .write_buffer(&render_device, &render_queue); + + phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata + .write_buffer(&render_device); + phase_indirect_parameters_buffers + .indexed + .gpu_metadata + .write_buffer(&render_device); + + phase_indirect_parameters_buffers + .indexed + .batch_sets + .write_buffer(&render_device, &render_queue); + phase_indirect_parameters_buffers + .non_indexed + .batch_sets + .write_buffer(&render_device, &render_queue); + } } #[cfg(test)] diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 214fdda13644e..c2751fc79ae0a 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -4,16 +4,16 @@ use bevy_ecs::{ system::{ResMut, SystemParam, SystemParamItem}, }; use bytemuck::Pod; +use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers; use nonmax::NonMaxU32; -use self::gpu_preprocessing::IndirectParametersBuffers; -use crate::{render_phase::PhaseItemExtraIndex, sync_world::MainEntity}; use crate::{ render_phase::{ - BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, SortedPhaseItem, - SortedRenderPhase, ViewBinnedRenderPhases, + BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItemExtraIndex, + SortedPhaseItem, SortedRenderPhase, ViewBinnedRenderPhases, }, render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, + sync_world::MainEntity, }; pub mod gpu_preprocessing; @@ -132,26 +132,29 @@ pub trait GetFullBatchData: GetBatchData { ) -> Option<(NonMaxU32, Option)>; /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the - /// GPU preprocessing phase will use, for the binning path. + /// GPU preprocessing phase will use. /// /// We already inserted the [`GetFullBatchData::BufferInputData`] during the /// extraction phase before we got here, so this function shouldn't need to - /// look up any render data. If CPU instance buffer building is in use, this - /// function will never be called. + /// look up any render data. + /// + /// This function is currently only called for unbatchable entities when GPU + /// instance buffer building is in use. For batchable entities, the uniform + /// index is written during queuing (e.g. in `queue_material_meshes`). In + /// the case of CPU instance buffer building, the CPU writes the uniforms, + /// so there's no index to return. fn get_binned_index( param: &SystemParamItem, query_item: MainEntity, ) -> Option; - /// Writes the [`gpu_preprocessing::IndirectParametersMetadata`] necessary - /// to draw this batch into the given metadata buffer at the given index. + /// Writes the [`gpu_preprocessing::IndirectParametersGpuMetadata`] + /// necessary to draw this batch into the given metadata buffer at the given + /// index. /// /// This is only used if GPU culling is enabled (which requires GPU /// preprocessing). /// - /// * `mesh_index` describes the index of the first mesh instance in this - /// batch in the `MeshInputUniform` buffer. - /// /// * `indexed` is true if the mesh is indexed or false if it's non-indexed. /// /// * `base_output_index` is the index of the first mesh instance in this @@ -167,11 +170,10 @@ pub trait GetFullBatchData: GetBatchData { /// * `indirect_parameters_offset` is the index in that buffer at which to /// write the metadata. fn write_batch_indirect_parameters_metadata( - mesh_index: u32, indexed: bool, base_output_index: u32, batch_set_index: Option, - indirect_parameters_buffers: &mut IndirectParametersBuffers, + indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers, indirect_parameters_offset: u32, ); } @@ -182,9 +184,9 @@ where BPI: BinnedPhaseItem, { for phase in phases.values_mut() { - phase.multidrawable_mesh_keys.sort_unstable(); - phase.batchable_mesh_keys.sort_unstable(); - phase.unbatchable_mesh_keys.sort_unstable(); + phase.multidrawable_meshes.sort_unstable_keys(); + phase.batchable_meshes.sort_unstable_keys(); + phase.unbatchable_meshes.sort_unstable_keys(); } } diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 6e0b893028f8d..8bbbff8dd9e46 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -1,6 +1,7 @@ //! Batching functionality when GPU preprocessing isn't in use. use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::entity::Entity; use bevy_ecs::resource::Resource; use bevy_ecs::system::{Res, ResMut, StaticSystemParam}; use smallvec::{smallvec, SmallVec}; @@ -107,11 +108,11 @@ pub fn batch_and_prepare_binned_render_phase( for phase in phases.values_mut() { // Prepare batchables. - for key in &phase.batchable_mesh_keys { + for bin in phase.batchable_meshes.values_mut() { let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![]; - for &(entity, main_entity) in &phase.batchable_mesh_values[key].entities { + for main_entity in bin.entities().keys() { let Some(buffer_data) = - GFBD::get_binned_batch_data(&system_param_item, main_entity) + GFBD::get_binned_batch_data(&system_param_item, *main_entity) else { continue; }; @@ -128,7 +129,7 @@ pub fn batch_and_prepare_binned_render_phase( == PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset) }) { batch_set.push(BinnedRenderPhaseBatch { - representative_entity: (entity, main_entity), + representative_entity: (Entity::PLACEHOLDER, *main_entity), instance_range: instance.index..instance.index, extra_index: PhaseItemExtraIndex::maybe_dynamic_offset( instance.dynamic_offset, @@ -155,11 +156,10 @@ pub fn batch_and_prepare_binned_render_phase( } // Prepare unbatchables. - for key in &phase.unbatchable_mesh_keys { - let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap(); - for &(_, main_entity) in &unbatchables.entities { + for unbatchables in phase.unbatchable_meshes.values_mut() { + for main_entity in unbatchables.entities.keys() { let Some(buffer_data) = - GFBD::get_binned_batch_data(&system_param_item, main_entity) + GFBD::get_binned_batch_data(&system_param_item, *main_entity) else { continue; }; diff --git a/crates/bevy_render/src/bindless.wgsl b/crates/bevy_render/src/bindless.wgsl new file mode 100644 index 0000000000000..05517a1746d29 --- /dev/null +++ b/crates/bevy_render/src/bindless.wgsl @@ -0,0 +1,37 @@ +// Defines the common arrays used to access bindless resources. +// +// This need to be kept up to date with the `BINDING_NUMBERS` table in +// `bindless.rs`. +// +// You access these by indexing into the bindless index table, and from there +// indexing into the appropriate binding array. For example, to access the base +// color texture of a `StandardMaterial` in bindless mode, write +// `bindless_textures_2d[materials[slot].base_color_texture]`, where +// `materials` is the bindless index table and `slot` is the index into that +// table (which can be found in the `Mesh`). + +#define_import_path bevy_render::bindless + +#ifdef BINDLESS + +// Binding 0 is the bindless index table. +// Filtering samplers. +@group(2) @binding(1) var bindless_samplers_filtering: binding_array; +// Non-filtering samplers (nearest neighbor). +@group(2) @binding(2) var bindless_samplers_non_filtering: binding_array; +// Comparison samplers (typically for shadow mapping). +@group(2) @binding(3) var bindless_samplers_comparison: binding_array; +// 1D textures. +@group(2) @binding(4) var bindless_textures_1d: binding_array>; +// 2D textures. +@group(2) @binding(5) var bindless_textures_2d: binding_array>; +// 2D array textures. +@group(2) @binding(6) var bindless_textures_2d_array: binding_array>; +// 3D textures. +@group(2) @binding(7) var bindless_textures_3d: binding_array>; +// Cubemap textures. +@group(2) @binding(8) var bindless_textures_cube: binding_array>; +// Cubemap array textures. +@group(2) @binding(9) var bindless_textures_cube_array: binding_array>; + +#endif // BINDLESS diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index dc977ceb8404f..298be27c5fa9e 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -942,7 +942,7 @@ pub fn camera_system( || camera.computed.old_sub_camera_view != camera.sub_camera_view { let new_computed_target_info = normalized_target.get_render_target_info( - &windows, + windows, &images, &manual_texture_views, ); @@ -1134,6 +1134,7 @@ pub fn extract_cameras( }) .collect(), }; + let mut commands = commands.entity(render_entity); commands.insert(( ExtractedCamera { diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 4dd04323ba5be..2b75dab047247 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -2,6 +2,7 @@ use core::fmt::Debug; use crate::{primitives::Frustum, view::VisibilitySystems}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; +use bevy_asset::AssetEvents; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4}; @@ -29,7 +30,9 @@ impl Plugin for CameraProjectionPlugin { .add_systems( PostUpdate, ( - crate::camera::camera_system.in_set(CameraUpdateSystem), + crate::camera::camera_system + .in_set(CameraUpdateSystem) + .before(AssetEvents), crate::view::update_frusta .in_set(VisibilitySystems::UpdateFrusta) .after(crate::camera::camera_system) diff --git a/crates/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl b/crates/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl index 7f4dd71f610f0..a597fb0537228 100644 --- a/crates/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl +++ b/crates/bevy_render/src/experimental/occlusion_culling/mesh_preprocess_types.wgsl @@ -15,12 +15,13 @@ struct MeshInput { first_index_index: u32, index_count: u32, current_skin_index: u32, - previous_skin_index: u32, // Low 16 bits: index of the material inside the bind group data. // High 16 bits: index of the lightmap in the binding array. material_and_lightmap_bind_group_slot: u32, - pad_a: u32, - pad_b: u32, + timestamp: u32, + // User supplied index to identify the mesh instance + tag: u32, + pad: u32, } // The `wgpu` indirect parameters structure. This is a union of two structures. @@ -46,12 +47,20 @@ struct IndirectParametersNonIndexed { first_instance: u32, } -struct IndirectParametersMetadata { - mesh_index: u32, +struct IndirectParametersCpuMetadata { base_output_index: u32, batch_set_index: u32, +} + +struct IndirectParametersGpuMetadata { + mesh_index: u32, +#ifdef WRITE_INDIRECT_PARAMETERS_METADATA early_instance_count: atomic, late_instance_count: atomic, +#else // WRITE_INDIRECT_PARAMETERS_METADATA + early_instance_count: u32, + late_instance_count: u32, +#endif // WRITE_INDIRECT_PARAMETERS_METADATA } struct IndirectBatchSet { diff --git a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs index 1c7fae1515863..6bdfb4a102bf7 100644 --- a/crates/bevy_render/src/experimental/occlusion_culling/mod.rs +++ b/crates/bevy_render/src/experimental/occlusion_culling/mod.rs @@ -4,15 +4,18 @@ //! Bevy. use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; -use bevy_ecs::{component::Component, prelude::ReflectComponent}; +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}; +use crate::{ + extract_component::ExtractComponent, + render_resource::{Shader, TextureView}, +}; /// The handle to the `mesh_preprocess_types.wgsl` compute shader. pub const MESH_PREPROCESS_TYPES_SHADER_HANDLE: Handle = - Handle::weak_from_u128(2720440370122465935); + weak_handle!("7bf7bdb1-ec53-4417-987f-9ec36533287c"); /// Enables GPU occlusion culling. /// @@ -85,3 +88,29 @@ impl Plugin for OcclusionCullingPlugin { #[derive(Component, ExtractComponent, Clone, Copy, Default, Reflect)] #[reflect(Component, Default)] pub struct OcclusionCulling; + +/// A render-world component that contains resources necessary to perform +/// occlusion culling on any view other than a camera. +/// +/// Bevy automatically places this component on views created for shadow +/// mapping. You don't ordinarily need to add this component yourself. +#[derive(Clone, Component)] +pub struct OcclusionCullingSubview { + /// A texture view of the Z-buffer. + pub depth_texture_view: TextureView, + /// The size of the texture along both dimensions. + /// + /// Because [`OcclusionCullingSubview`] is only currently used for shadow + /// maps, they're guaranteed to have sizes equal to a power of two, so we + /// don't have to store the two dimensions individually here. + pub depth_texture_size: u32, +} + +/// A render-world component placed on each camera that stores references to all +/// entities other than cameras that need occlusion culling. +/// +/// Bevy automatically places this component on cameras that are drawing +/// shadows, when those shadows come from lights with occlusion culling enabled. +/// You don't ordinarily need to add this component yourself. +#[derive(Clone, Component)] +pub struct OcclusionCullingSubviewEntities(pub Vec); diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 64e744775ffaf..f77199842828c 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -8,6 +8,7 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_ecs::{ + bundle::NoBundleEffect, component::Component, prelude::*, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, @@ -53,7 +54,7 @@ pub trait ExtractComponent: Component { /// /// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases /// such as tuples of components as output. - type Out: Bundle; + type Out: Bundle; // TODO: https://github.com/rust-lang/rust/issues/29661 // type Out: Component = Self; diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 65d5bbc28fd7f..b21b37ab8d963 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -6,13 +6,14 @@ use crate::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Handle}; +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 = Handle::weak_from_u128(17924628719070609599); +pub const GLOBALS_TYPE_HANDLE: Handle = + weak_handle!("9e22a765-30ca-4070-9a4c-34ac08f1c0e7"); pub struct GlobalsPlugin; diff --git a/crates/bevy_render/src/gpu_readback.rs b/crates/bevy_render/src/gpu_readback.rs index b7da4800acb5c..012ec1a6cb846 100644 --- a/crates/bevy_render/src/gpu_readback.rs +++ b/crates/bevy_render/src/gpu_readback.rs @@ -1,7 +1,10 @@ use crate::{ extract_component::ExtractComponentPlugin, render_asset::RenderAssets, - render_resource::{Buffer, BufferUsages, Extent3d, ImageDataLayout, Texture, TextureFormat}, + render_resource::{ + Buffer, BufferUsages, CommandEncoder, Extent3d, TexelCopyBufferLayout, Texture, + TextureFormat, + }, renderer::{render_system, RenderDevice}, storage::{GpuShaderStorageBuffer, ShaderStorageBuffer}, sync_world::MainEntity, @@ -28,7 +31,6 @@ use encase::internal::ReadFrom; use encase::private::Reader; use encase::ShaderType; use tracing::warn; -use wgpu::CommandEncoder; /// A plugin that enables reading back gpu buffers and textures to the cpu. pub struct GpuReadbackPlugin { @@ -185,7 +187,7 @@ impl GpuReadbackBufferPool { enum ReadbackSource { Texture { texture: Texture, - layout: ImageDataLayout, + layout: TexelCopyBufferLayout, size: Extent3d, }, Buffer { @@ -295,7 +297,7 @@ pub(crate) fn submit_readback_commands(world: &World, command_encoder: &mut Comm } => { command_encoder.copy_texture_to_buffer( texture.as_image_copy(), - wgpu::ImageCopyBuffer { + wgpu::TexelCopyBufferInfo { buffer: &readback.buffer, layout: *layout, }, @@ -354,9 +356,9 @@ pub(crate) const fn get_aligned_size(extent: Extent3d, pixel_size: u32) -> u32 { extent.height * align_byte_size(extent.width * pixel_size) * extent.depth_or_array_layers } -/// Get a [`ImageDataLayout`] aligned such that the image can be copied into a buffer. -pub(crate) fn layout_data(extent: Extent3d, format: TextureFormat) -> ImageDataLayout { - ImageDataLayout { +/// Get a [`TexelCopyBufferLayout`] aligned such that the image can be copied into a buffer. +pub(crate) fn layout_data(extent: Extent3d, format: TextureFormat) -> TexelCopyBufferLayout { + TexelCopyBufferLayout { bytes_per_row: if extent.height > 1 || extent.depth_or_array_layers > 1 { // 1 = 1 row Some(get_aligned_size( diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0f9c504edeeb9..4a91686dfb243 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -19,6 +19,9 @@ compile_error!("bevy_render cannot compile for a 16-bit platform."); extern crate alloc; extern crate core; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_render; + pub mod alpha; pub mod batching; pub mod camera; @@ -97,8 +100,9 @@ use crate::{ }; use alloc::sync::Arc; use bevy_app::{App, AppLabel, Plugin, SubApp}; -use bevy_asset::{load_internal_asset, AssetApp, AssetServer, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetApp, AssetServer, Handle}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; +use bitflags::bitflags; use core::ops::{Deref, DerefMut}; use std::sync::Mutex; use tracing::debug; @@ -117,12 +121,21 @@ pub struct RenderPlugin { /// If `true`, disables asynchronous pipeline compilation. /// This has no effect on macOS, Wasm, iOS, or without the `multi_threaded` feature. pub synchronous_pipeline_compilation: bool, - /// If true, this sets the `COPY_SRC` flag on indirect draw parameters so - /// that they can be read back to CPU. - /// - /// This is a debugging feature that may reduce performance. It primarily - /// exists for the `occlusion_culling` example. - pub allow_copies_from_indirect_parameters: bool, + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, +} + +bitflags! { + /// Debugging flags that can optionally be set when constructing the renderer. + #[derive(Clone, Copy, PartialEq, Default, Debug)] + pub struct RenderDebugFlags: u8 { + /// If true, this sets the `COPY_SRC` flag on indirect draw parameters + /// so that they can be read back to CPU. + /// + /// This is a debugging feature that may reduce performance. It + /// primarily exists for the `occlusion_culling` example. + const ALLOW_COPIES_FROM_INDIRECT_PARAMETERS = 1; + } } /// The systems sets of the default [`App`] rendering schedule. @@ -143,6 +156,9 @@ pub enum RenderSet { Queue, /// A sub-set within [`Queue`](RenderSet::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 + /// become invisible or changed phases are removed from the bins. + QueueSweep, // TODO: This could probably be moved in favor of a system ordering // abstraction in `Render` or `Queue` /// Sort the [`SortedRenderPhase`](render_phase::SortedRenderPhase)s and @@ -153,6 +169,9 @@ pub enum RenderSet { Prepare, /// A sub-set within [`Prepare`](RenderSet::Prepare) for initializing buffers, textures and uniforms for use in bind groups. PrepareResources, + /// Collect phase buffers after + /// [`PrepareResources`](RenderSet::PrepareResources) has run. + PrepareResourcesCollectPhaseBuffers, /// Flush buffers after [`PrepareResources`](RenderSet::PrepareResources), but before [`PrepareBindGroups`](RenderSet::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). @@ -198,12 +217,18 @@ impl Render { schedule.configure_sets((ExtractCommands, PrepareAssets, PrepareMeshes, Prepare).chain()); schedule.configure_sets( - QueueMeshes + (QueueMeshes, QueueSweep) + .chain() .in_set(Queue) .after(prepare_assets::), ); schedule.configure_sets( - (PrepareResources, PrepareResourcesFlush, PrepareBindGroups) + ( + PrepareResources, + PrepareResourcesCollectPhaseBuffers, + PrepareResourcesFlush, + PrepareBindGroups, + ) .chain() .in_set(Prepare), ); @@ -259,10 +284,13 @@ struct FutureRenderResources(Arc>>); pub struct RenderApp; pub const INSTANCE_INDEX_SHADER_HANDLE: Handle = - Handle::weak_from_u128(10313207077636615845); -pub const MATHS_SHADER_HANDLE: Handle = Handle::weak_from_u128(10665356303104593376); + 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 = - Handle::weak_from_u128(1844674407370955161); + 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. @@ -294,11 +322,17 @@ impl Plugin for RenderPlugin { .cloned(); let settings = render_creation.clone(); let async_renderer = async move { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends, - dx12_shader_compiler: settings.dx12_shader_compiler.clone(), flags: settings.instance_flags, - gles_minor_version: settings.gles3_minor_version, + backend_options: wgpu::BackendOptions { + gl: wgpu::GlBackendOptions { + gles_minor_version: settings.gles3_minor_version, + }, + dx12: wgpu::Dx12BackendOptions { + shader_compiler: settings.dx12_shader_compiler.clone(), + }, + }, }); let surface = primary_window.and_then(|wrapper| { @@ -366,7 +400,7 @@ impl Plugin for RenderPlugin { GlobalsPlugin, MorphPlugin, BatchingPlugin { - allow_copies_from_indirect_parameters: self.allow_copies_from_indirect_parameters, + debug_flags: self.debug_flags, }, SyncWorldPlugin, StoragePlugin, @@ -402,6 +436,12 @@ impl Plugin for RenderPlugin { "color_operations.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + BINDLESS_SHADER_HANDLE, + "bindless.wgsl", + Shader::from_wgsl + ); if let Some(future_render_resources) = app.world_mut().remove_resource::() { diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index b703792b65cf1..712fbc6272e8c 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::{HashMap, HashSet}; +use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_utils::default; use offset_allocator::{Allocation, Allocator}; use tracing::error; @@ -196,7 +196,7 @@ struct GeneralSlab { element_layout: ElementLayout, /// The size of this slab in slots. - slot_capacity: u32, + current_slot_capacity: u32, } /// A slab that contains a single object. @@ -224,6 +224,18 @@ enum ElementClass { Index, } +/// The results of [`GeneralSlab::grow_if_necessary`]. +enum SlabGrowthResult { + /// The mesh data already fits in the slab; the slab doesn't need to grow. + NoGrowthNeeded, + /// The slab needed to grow. + /// + /// The [`SlabToReallocate`] contains the old capacity of the slab. + NeededGrowth(SlabToReallocate), + /// The slab wanted to grow but couldn't because it hit its maximum size. + CantGrow, +} + /// Information about the size of individual elements (vertices or indices) /// within a slab. /// @@ -278,9 +290,8 @@ struct SlabsToReallocate(HashMap); /// reallocated. #[derive(Default)] struct SlabToReallocate { - /// Maps all allocations that need to be relocated to their positions within - /// the *new* slab. - allocations_to_copy: HashMap, SlabAllocation>, + /// The capacity of the slab before we decided to grow it. + old_slot_capacity: u32, } impl Display for SlabId { @@ -694,32 +705,39 @@ impl MeshAllocator { // and try to allocate the mesh inside them. We go with the first one // that succeeds. let mut mesh_allocation = None; - 'slab: for &slab_id in &*candidate_slabs { - loop { - let Some(Slab::General(ref mut slab)) = self.slabs.get_mut(&slab_id) else { - unreachable!("Slab not found") - }; + for &slab_id in &*candidate_slabs { + let Some(Slab::General(ref mut slab)) = self.slabs.get_mut(&slab_id) else { + unreachable!("Slab not found") + }; - if let Some(allocation) = slab.allocator.allocate(data_slot_count) { - mesh_allocation = Some(MeshAllocation { - slab_id, - slab_allocation: SlabAllocation { - allocation, - slot_count: data_slot_count, - }, - }); - break 'slab; - } + let Some(allocation) = slab.allocator.allocate(data_slot_count) else { + continue; + }; - // Try to grow the slab. If this fails, the slab is full; go on - // to the next slab. - match slab.try_grow(settings) { - Ok(new_mesh_allocation_records) => { - slabs_to_grow.insert(slab_id, new_mesh_allocation_records); + // Try to fit the object in the slab, growing if necessary. + match slab.grow_if_necessary(allocation.offset + data_slot_count, settings) { + SlabGrowthResult::NoGrowthNeeded => {} + SlabGrowthResult::NeededGrowth(slab_to_reallocate) => { + // If we already grew the slab this frame, don't replace the + // `SlabToReallocate` entry. We want to keep the entry + // corresponding to the size that the slab had at the start + // of the frame, so that we can copy only the used portion + // of the initial buffer to the new one. + if let Entry::Vacant(vacant_entry) = slabs_to_grow.entry(slab_id) { + vacant_entry.insert(slab_to_reallocate); } - Err(()) => continue 'slab, } + SlabGrowthResult::CantGrow => continue, } + + mesh_allocation = Some(MeshAllocation { + slab_id, + slab_allocation: SlabAllocation { + allocation, + slot_count: data_slot_count, + }, + }); + break; } // If we still have no allocation, make a new slab. @@ -774,10 +792,11 @@ impl MeshAllocator { /// Reallocates a slab that needs to be resized, or allocates a new slab. /// - /// This performs the actual growth operation that [`GeneralSlab::try_grow`] - /// scheduled. We do the growth in two phases so that, if a slab grows - /// multiple times in the same frame, only one new buffer is reallocated, - /// rather than reallocating the buffer multiple times. + /// This performs the actual growth operation that + /// [`GeneralSlab::grow_if_necessary`] scheduled. We do the growth in two + /// phases so that, if a slab grows multiple times in the same frame, only + /// one new buffer is reallocated, rather than reallocating the buffer + /// multiple times. fn reallocate_slab( &mut self, render_device: &RenderDevice, @@ -805,38 +824,28 @@ impl MeshAllocator { slab_id, buffer_usages_to_str(buffer_usages) )), - size: slab.slot_capacity as u64 * slab.element_layout.slot_size(), + size: slab.current_slot_capacity as u64 * slab.element_layout.slot_size(), usage: buffer_usages, mapped_at_creation: false, }); slab.buffer = Some(new_buffer.clone()); + let Some(old_buffer) = old_buffer else { return }; + // In order to do buffer copies, we need a command encoder. let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor { label: Some("slab resize encoder"), }); - // If we have no objects to copy over, we're done. - let Some(old_buffer) = old_buffer else { - return; - }; - - for (mesh_id, src_slab_allocation) in &mut slab.resident_allocations { - let Some(dest_slab_allocation) = slab_to_grow.allocations_to_copy.get(mesh_id) else { - continue; - }; - - encoder.copy_buffer_to_buffer( - &old_buffer, - src_slab_allocation.allocation.offset as u64 * slab.element_layout.slot_size(), - &new_buffer, - dest_slab_allocation.allocation.offset as u64 * slab.element_layout.slot_size(), - dest_slab_allocation.slot_count as u64 * slab.element_layout.slot_size(), - ); - // Now that we've done the copy, we can update the allocation record. - *src_slab_allocation = dest_slab_allocation.clone(); - } + // Copy the data from the old buffer into the new one. + encoder.copy_buffer_to_buffer( + &old_buffer, + 0, + &new_buffer, + 0, + slab_to_grow.old_slot_capacity as u64 * slab.element_layout.slot_size(), + ); let command_buffer = encoder.finish(); render_queue.submit([command_buffer]); @@ -872,16 +881,19 @@ impl GeneralSlab { layout: ElementLayout, data_slot_count: u32, ) -> GeneralSlab { - let slab_slot_capacity = (settings.min_slab_size.div_ceil(layout.slot_size()) as u32) + let initial_slab_slot_capacity = (settings.min_slab_size.div_ceil(layout.slot_size()) + as u32) + .max(offset_allocator::ext::min_allocator_size(data_slot_count)); + let max_slab_slot_capacity = (settings.max_slab_size.div_ceil(layout.slot_size()) as u32) .max(offset_allocator::ext::min_allocator_size(data_slot_count)); let mut new_slab = GeneralSlab { - allocator: Allocator::new(slab_slot_capacity), + allocator: Allocator::new(max_slab_slot_capacity), buffer: None, resident_allocations: HashMap::default(), pending_allocations: HashMap::default(), element_layout: layout, - slot_capacity: slab_slot_capacity, + current_slot_capacity: initial_slab_slot_capacity, }; // This should never fail. @@ -898,68 +910,40 @@ impl GeneralSlab { new_slab } - /// Attempts to grow a slab that's just run out of space. + /// Checks to see if the size of this slab is at least `new_size_in_slots` + /// and grows the slab if it isn't. /// - /// Returns a structure the allocations that need to be relocated if the - /// growth succeeded. If the slab is full, returns `Err`. - fn try_grow(&mut self, settings: &MeshAllocatorSettings) -> Result { - // In extremely rare cases due to allocator fragmentation, it may happen - // that we fail to re-insert every object that was in the slab after - // growing it. Even though this will likely never happen, we use this - // loop to handle this unlikely event properly if it does. - 'grow: loop { - let new_slab_slot_capacity = ((self.slot_capacity as f64 * settings.growth_factor) - .ceil() as u32) - .min((settings.max_slab_size / self.element_layout.slot_size()) as u32); - if new_slab_slot_capacity == self.slot_capacity { - // The slab is full. - return Err(()); - } - - // Grow the slab. - self.allocator = Allocator::new(new_slab_slot_capacity); - self.slot_capacity = new_slab_slot_capacity; - - let mut slab_to_grow = SlabToReallocate::default(); - - // Place every resident allocation that was in the old slab in the - // new slab. - for (allocated_mesh_id, old_allocation_range) in &self.resident_allocations { - let allocation_size = old_allocation_range.slot_count; - match self.allocator.allocate(allocation_size) { - Some(allocation) => { - slab_to_grow.allocations_to_copy.insert( - *allocated_mesh_id, - SlabAllocation { - allocation, - slot_count: allocation_size, - }, - ); - } - None => { - // We failed to insert one of the allocations that we - // had before. - continue 'grow; - } - } - } + /// The returned [`SlabGrowthResult`] describes whether the slab needed to + /// grow and whether, if so, it was successful in doing so. + fn grow_if_necessary( + &mut self, + new_size_in_slots: u32, + settings: &MeshAllocatorSettings, + ) -> SlabGrowthResult { + // Is the slab big enough already? + let initial_slot_capacity = self.current_slot_capacity; + if self.current_slot_capacity >= new_size_in_slots { + return SlabGrowthResult::NoGrowthNeeded; + } - // Move every allocation that was pending in the old slab to the new - // slab. - for slab_allocation in self.pending_allocations.values_mut() { - let allocation_size = slab_allocation.slot_count; - match self.allocator.allocate(allocation_size) { - Some(allocation) => slab_allocation.allocation = allocation, - None => { - // We failed to insert one of the allocations that we - // had before. - continue 'grow; - } - } + // Try to grow in increments of `MeshAllocatorSettings::growth_factor` + // until we're big enough. + while self.current_slot_capacity < new_size_in_slots { + let new_slab_slot_capacity = + ((self.current_slot_capacity as f64 * settings.growth_factor).ceil() as u32) + .min((settings.max_slab_size / self.element_layout.slot_size()) as u32); + if new_slab_slot_capacity == self.current_slot_capacity { + // The slab is full. + return SlabGrowthResult::CantGrow; } - return Ok(slab_to_grow); + self.current_slot_capacity = new_slab_slot_capacity; } + + // Tell our caller what we did. + SlabGrowthResult::NeededGrowth(SlabToReallocate { + old_slot_capacity: initial_slot_capacity, + }) } } diff --git a/crates/bevy_render/src/mesh/components.rs b/crates/bevy_render/src/mesh/components.rs index 9bfa05968d733..b5b03ac2b8f27 100644 --- a/crates/bevy_render/src/mesh/components.rs +++ b/crates/bevy_render/src/mesh/components.rs @@ -2,7 +2,7 @@ use crate::{ mesh::Mesh, view::{self, Visibility, VisibilityClass}, }; -use bevy_asset::{AssetEvent, AssetId, Handle}; +use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChangesMut, component::Component, event::EventReader, prelude::require, @@ -58,6 +58,14 @@ impl From<&Mesh2d> for AssetId { } } +impl AsAssetId for Mesh2d { + type Asset = Mesh; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// A component for 3D meshes. Requires a [`MeshMaterial3d`] to be rendered, commonly using a [`StandardMaterial`]. /// /// [`MeshMaterial3d`]: @@ -106,6 +114,14 @@ impl From<&Mesh3d> for AssetId { } } +impl AsAssetId for Mesh3d { + type Asset = Mesh; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// A system that marks a [`Mesh3d`] as changed if the associated [`Mesh`] asset /// has changed. /// @@ -134,3 +150,8 @@ pub fn mark_3d_meshes_as_changed_if_their_assets_changed( } } } + +/// A component that stores an arbitrary index used to identify the mesh instance when rendering. +#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)] +#[reflect(Component, Default)] +pub struct MeshTag(pub u32); diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index c8f45e787b51a..40fb08d987bc6 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, AssetId, RenderAssetUsages}; +use bevy_asset::{AssetApp, AssetEvents, AssetId, RenderAssetUsages}; use bevy_ecs::{ prelude::*, system::{ @@ -21,7 +21,7 @@ use bevy_ecs::{ SystemParamItem, }, }; -pub use components::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d}; +pub use components::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d, MeshTag}; use wgpu::IndexFormat; /// Registers all [`MeshBuilder`] types. @@ -72,7 +72,8 @@ impl Plugin for MeshPlugin { .add_systems( PostUpdate, mark_3d_meshes_as_changed_if_their_assets_changed - .ambiguous_with(VisibilitySystems::CalculateBounds), + .ambiguous_with(VisibilitySystems::CalculateBounds) + .before(AssetEvents), ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -195,7 +196,7 @@ impl RenderAsset for RenderMesh { let mut vertex_size = 0; for attribute_data in mesh.attributes() { let vertex_format = attribute_data.0.format; - vertex_size += vertex_format.get_size() as usize; + vertex_size += vertex_format.size() as usize; } let vertex_count = mesh.count_vertices(); diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 4343fda6d3941..a12d336018508 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -332,7 +332,9 @@ where let view = match self.view.get_manual(world, view) { Ok(view) => view, Err(err) => match err { - QueryEntityError::NoSuchEntity(_, _) => return Err(DrawError::ViewEntityNotFound), + QueryEntityError::EntityDoesNotExist(_) => { + return Err(DrawError::ViewEntityNotFound) + } QueryEntityError::QueryDoesNotMatch(_, _) | QueryEntityError::AliasedMutability(_) => { return Err(DrawError::InvalidViewQuery) diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index f8a2f7f3ad24d..ede05bb2fc618 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -30,19 +30,27 @@ mod rangefinder; 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_utils::default; pub use draw::*; pub use draw_state::*; use encase::{internal::WriteInto, ShaderSize}; +use fixedbitset::{Block, FixedBitSet}; +use indexmap::IndexMap; use nonmax::NonMaxU32; pub use rangefinder::*; use wgpu::Features; -use crate::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; +use crate::batching::gpu_preprocessing::{ + GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers, + PhaseIndirectParametersBuffers, +}; use crate::renderer::RenderDevice; -use crate::sync_world::MainEntity; +use crate::sync_world::{MainEntity, MainEntityHashMap}; use crate::view::RetainedViewEntity; +use crate::RenderDebugFlags; use crate::{ batching::{ self, @@ -88,13 +96,7 @@ pub struct BinnedRenderPhase where BPI: BinnedPhaseItem, { - /// A list of `BatchSetKey`s for batchable, multidrawable items. - /// - /// These are accumulated in `queue_material_meshes` and then sorted in - /// `batching::sort_binned_render_phase`. - pub multidrawable_mesh_keys: Vec, - - /// The multidrawable bins themselves. + /// The multidrawable bins. /// /// Each batch set key maps to a *batch set*, which in this case is a set of /// meshes that can be drawn together in one multidraw call. Each batch set @@ -107,36 +109,18 @@ where /// the same pipeline. The first bin, corresponding to the cubes, will have /// two entities in it. The second bin, corresponding to the sphere, will /// have one entity in it. - pub multidrawable_mesh_values: HashMap>, - - /// A list of `BinKey`s for batchable items that aren't multidrawable. - /// - /// These are accumulated in `queue_material_meshes` and then sorted in - /// `batch_and_prepare_binned_render_phase`. - /// - /// Usually, batchable items aren't multidrawable due to platform or - /// hardware limitations. However, it's also possible to have batchable - /// items alongside multidrawable items with custom mesh pipelines. See - /// `specialized_mesh_pipeline` for an example. - pub batchable_mesh_keys: Vec<(BPI::BatchSetKey, BPI::BinKey)>, + pub multidrawable_meshes: IndexMap>, /// The bins corresponding to batchable items that aren't multidrawable. /// - /// For multidrawable entities, use `multidrawable_mesh_values`; for + /// For multidrawable entities, use `multidrawable_meshes`; for /// unbatchable entities, use `unbatchable_values`. - pub batchable_mesh_values: HashMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, - - /// A list of `BinKey`s for unbatchable items. - /// - /// These are accumulated in `queue_material_meshes` and then sorted in - /// `batch_and_prepare_binned_render_phase`. - pub unbatchable_mesh_keys: Vec<(BPI::BatchSetKey, BPI::BinKey)>, + pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, /// The unbatchable bins. /// /// Each entity here is rendered in a separate drawcall. - pub unbatchable_mesh_values: - HashMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, + pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>, /// Items in the bin that aren't meshes at all. /// @@ -145,7 +129,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: Vec<(BPI::BatchSetKey, BPI::BinKey, (Entity, MainEntity))>, + pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>, /// Information on each batch set. /// @@ -158,14 +142,82 @@ where /// Multidrawable entities come first, then batchable entities, then /// unbatchable entities. pub(crate) batch_sets: BinnedRenderPhaseBatchSets, + + /// The batch and bin key for each entity. + /// + /// We retain these so that, when the entity changes, + /// [`Self::sweep_old_entities`] can quickly find the bin it was located in + /// and remove it. + cached_entity_bin_keys: IndexMap, EntityHash>, + + /// The set of indices in [`Self::cached_entity_bin_keys`] that are + /// confirmed to be up to date. + /// + /// Note that each bit in this bit set refers to an *index* in the + /// [`IndexMap`] (i.e. a bucket in the hash table). They aren't entity IDs. + valid_cached_entity_bin_keys: FixedBitSet, + + /// The set of entities that changed bins this frame. + /// + /// An entity will only be present in this list if it was in one bin on the + /// previous frame and is in a new bin on this frame. Each list entry + /// specifies the bin the entity used to be in. We use this in order to + /// remove the entity from the old bin during + /// [`BinnedRenderPhase::sweep_old_entities`]. + entities_that_changed_bins: Vec>, } /// All entities that share a mesh and a material and can be batched as part of /// a [`BinnedRenderPhase`]. #[derive(Default)] pub struct RenderBin { - /// A list of the entities in each bin. - pub entities: Vec<(Entity, MainEntity)>, + /// A list of the entities in each bin, along with their cached + /// [`InputUniformIndex`]. + entities: IndexMap, +} + +/// Information that we track about an entity that was in one bin on the +/// previous frame and is in a different bin this frame. +struct EntityThatChangedBins +where + BPI: BinnedPhaseItem, +{ + /// The entity. + main_entity: MainEntity, + /// The key that identifies the bin that this entity used to be in. + old_bin_key: CachedBinKey, +} + +/// Information that we keep about an entity currently within a bin. +pub struct CachedBinKey +where + BPI: BinnedPhaseItem, +{ + /// The key of the batch set containing the entity. + pub batch_set_key: BPI::BatchSetKey, + /// The key of the bin containing the entity. + pub bin_key: BPI::BinKey, + /// The type of render phase that we use to render the entity: multidraw, + /// plain batch, etc. + pub phase_type: BinnedRenderPhaseType, + /// The last modified tick of the entity. + /// + /// We use this to detect when the entity needs to be invalidated. + pub change_tick: Tick, +} + +impl Clone for CachedBinKey +where + BPI: BinnedPhaseItem, +{ + fn clone(&self) -> Self { + CachedBinKey { + batch_set_key: self.batch_set_key.clone(), + bin_key: self.bin_key.clone(), + phase_type: self.phase_type, + change_tick: self.change_tick, + } + } } /// How we store and render the batch sets. @@ -233,7 +285,7 @@ pub struct BinnedRenderPhaseBatch { /// Information about the unbatchable entities in a bin. pub struct UnbatchableBinnedEntities { /// The entities. - pub entities: Vec<(Entity, MainEntity)>, + pub entities: MainEntityHashMap, /// The GPU array buffer indices of each unbatchable binned entity. pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet, @@ -337,13 +389,13 @@ impl ViewBinnedRenderPhases where BPI: BinnedPhaseItem, { - pub fn insert_or_clear( + pub fn prepare_for_new_frame( &mut self, retained_view_entity: RetainedViewEntity, gpu_preprocessing: GpuPreprocessingMode, ) { match self.entry(retained_view_entity) { - Entry::Occupied(mut entry) => entry.get_mut().clear(), + Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(), Entry::Vacant(entry) => { entry.insert(BinnedRenderPhase::::new(gpu_preprocessing)); } @@ -351,6 +403,19 @@ where } } +/// The index of the uniform describing this object in the GPU buffer, when GPU +/// preprocessing is enabled. +/// +/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in +/// the buffer. +/// +/// This field is ignored if GPU preprocessing isn't in use, such as (currently) +/// in the case of 2D meshes. In that case, it can be safely set to +/// [`core::default::Default::default`]. +#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)] +#[repr(transparent)] +pub struct InputUniformIndex(pub u32); + impl BinnedRenderPhase where BPI: BinnedPhaseItem, @@ -365,27 +430,25 @@ where batch_set_key: BPI::BatchSetKey, bin_key: BPI::BinKey, (entity, main_entity): (Entity, MainEntity), + input_uniform_index: InputUniformIndex, phase_type: BinnedRenderPhaseType, + change_tick: Tick, ) { match phase_type { BinnedRenderPhaseType::MultidrawableMesh => { - match self.multidrawable_mesh_values.entry(batch_set_key.clone()) { - Entry::Occupied(mut entry) => { + match self.multidrawable_meshes.entry(batch_set_key.clone()) { + indexmap::map::Entry::Occupied(mut entry) => { entry .get_mut() - .entry(bin_key) + .entry(bin_key.clone()) .or_default() - .entities - .push((entity, main_entity)); + .insert(main_entity, input_uniform_index); } - Entry::Vacant(entry) => { - self.multidrawable_mesh_keys.push(batch_set_key); - let mut new_batch_set = HashMap::default(); + indexmap::map::Entry::Vacant(entry) => { + let mut new_batch_set = IndexMap::default(); new_batch_set.insert( - bin_key, - RenderBin { - entities: vec![(entity, main_entity)], - }, + bin_key.clone(), + RenderBin::from_entity(main_entity, input_uniform_index), ); entry.insert(new_batch_set); } @@ -394,33 +457,31 @@ where BinnedRenderPhaseType::BatchableMesh => { match self - .batchable_mesh_values + .batchable_meshes .entry((batch_set_key.clone(), bin_key.clone()).clone()) { - Entry::Occupied(mut entry) => { - entry.get_mut().entities.push((entity, main_entity)); + indexmap::map::Entry::Occupied(mut entry) => { + entry.get_mut().insert(main_entity, input_uniform_index); } - Entry::Vacant(entry) => { - self.batchable_mesh_keys.push((batch_set_key, bin_key)); - entry.insert(RenderBin { - entities: vec![(entity, main_entity)], - }); + indexmap::map::Entry::Vacant(entry) => { + entry.insert(RenderBin::from_entity(main_entity, input_uniform_index)); } } } BinnedRenderPhaseType::UnbatchableMesh => { match self - .unbatchable_mesh_values + .unbatchable_meshes .entry((batch_set_key.clone(), bin_key.clone())) { - Entry::Occupied(mut entry) => { - entry.get_mut().entities.push((entity, main_entity)); + indexmap::map::Entry::Occupied(mut entry) => { + entry.get_mut().entities.insert(main_entity, entity); } - Entry::Vacant(entry) => { - self.unbatchable_mesh_keys.push((batch_set_key, bin_key)); + indexmap::map::Entry::Vacant(entry) => { + let mut entities = MainEntityHashMap::default(); + entities.insert(main_entity, entity); entry.insert(UnbatchableBinnedEntities { - entities: vec![(entity, main_entity)], + entities, buffer_indices: default(), }); } @@ -429,10 +490,47 @@ where BinnedRenderPhaseType::NonMesh => { // We don't process these items further. - self.non_mesh_items - .push((batch_set_key, bin_key, (entity, main_entity))); + match self + .non_mesh_items + .entry((batch_set_key.clone(), bin_key.clone()).clone()) + { + indexmap::map::Entry::Occupied(mut entry) => { + entry.get_mut().insert(main_entity, input_uniform_index); + } + indexmap::map::Entry::Vacant(entry) => { + entry.insert(RenderBin::from_entity(main_entity, input_uniform_index)); + } + } } } + + let new_bin_key = CachedBinKey { + batch_set_key, + bin_key, + phase_type, + change_tick, + }; + + let (index, old_bin_key) = self + .cached_entity_bin_keys + .insert_full(main_entity, new_bin_key.clone()); + + // If the entity changed bins, record its old bin so that we can remove + // the entity from it. + if let Some(old_bin_key) = old_bin_key { + if old_bin_key.batch_set_key != new_bin_key.batch_set_key + || old_bin_key.bin_key != new_bin_key.bin_key + || old_bin_key.phase_type != new_bin_key.phase_type + { + self.entities_that_changed_bins.push(EntityThatChangedBins { + main_entity, + old_bin_key, + }); + } + } + + // Mark the entity as valid. + self.valid_cached_entity_bin_keys.grow_and_insert(index); } /// Encodes the GPU commands needed to render all entities in this phase. @@ -474,10 +572,10 @@ where match self.batch_sets { BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => { - debug_assert_eq!(self.batchable_mesh_keys.len(), batch_sets.len()); + debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len()); for ((batch_set_key, bin_key), batch_set) in - self.batchable_mesh_keys.iter().zip(batch_sets.iter()) + self.batchable_meshes.keys().zip(batch_sets.iter()) { for batch in batch_set { let binned_phase_item = BPI::new( @@ -502,7 +600,7 @@ where BinnedRenderPhaseBatchSets::Direct(ref batch_set) => { for (batch, (batch_set_key, bin_key)) in - batch_set.iter().zip(self.batchable_mesh_keys.iter()) + batch_set.iter().zip(self.batchable_meshes.keys()) { let binned_phase_item = BPI::new( batch_set_key.clone(), @@ -525,11 +623,11 @@ where BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => { for (batch_set_key, batch_set) in self - .multidrawable_mesh_keys - .iter() + .multidrawable_meshes + .keys() .chain( - self.batchable_mesh_keys - .iter() + self.batchable_meshes + .keys() .map(|(batch_set_key, _)| batch_set_key), ) .zip(batch_sets.iter()) @@ -586,10 +684,10 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - for (batch_set_key, bin_key) in &self.unbatchable_mesh_keys { + for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() { let unbatchable_entities = - &self.unbatchable_mesh_values[&(batch_set_key.clone(), bin_key.clone())]; - for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() { + &self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())]; + for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() { let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices { UnbatchableBinnedEntityIndexSet::NoEntities => { // Shouldn't happen… @@ -622,7 +720,7 @@ where let binned_phase_item = BPI::new( batch_set_key.clone(), bin_key.clone(), - entity, + (*entity.1, *entity.0), unbatchable_dynamic_offset.instance_index ..(unbatchable_dynamic_offset.instance_index + 1), unbatchable_dynamic_offset.extra_index, @@ -652,44 +750,195 @@ where let draw_functions = world.resource::>(); let mut draw_functions = draw_functions.write(); - for &(ref batch_set_key, ref bin_key, entity) in &self.non_mesh_items { - // 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, - 0..1, - PhaseItemExtraIndex::None, - ); + for ((batch_set_key, bin_key), bin) in &self.non_mesh_items { + for &entity in bin.entities.keys() { + // 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), + 0..1, + PhaseItemExtraIndex::None, + ); - let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) - else { - continue; - }; + let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function()) + else { + continue; + }; - draw_function.draw(world, render_pass, view, &binned_phase_item)?; + draw_function.draw(world, render_pass, view, &binned_phase_item)?; + } } Ok(()) } pub fn is_empty(&self) -> bool { - self.multidrawable_mesh_keys.is_empty() - && self.batchable_mesh_keys.is_empty() - && self.unbatchable_mesh_keys.is_empty() + self.multidrawable_meshes.is_empty() + && self.batchable_meshes.is_empty() + && self.unbatchable_meshes.is_empty() && self.non_mesh_items.is_empty() } - pub fn clear(&mut self) { - self.multidrawable_mesh_keys.clear(); - self.multidrawable_mesh_values.clear(); - self.batchable_mesh_keys.clear(); - self.batchable_mesh_values.clear(); - self.unbatchable_mesh_keys.clear(); - self.unbatchable_mesh_values.clear(); - self.non_mesh_items.clear(); + pub fn prepare_for_new_frame(&mut self) { self.batch_sets.clear(); + + self.valid_cached_entity_bin_keys.clear(); + self.valid_cached_entity_bin_keys + .grow(self.cached_entity_bin_keys.len()); + self.valid_cached_entity_bin_keys + .set_range(self.cached_entity_bin_keys.len().., true); + + self.entities_that_changed_bins.clear(); + } + + /// Checks to see whether the entity is in a bin and returns true if it's + /// both in a bin and up to date. + /// + /// If this function returns true, we also add the entry to the + /// `valid_cached_entity_bin_keys` list. + pub fn validate_cached_entity( + &mut self, + visible_entity: MainEntity, + current_change_tick: Tick, + ) -> bool { + if let indexmap::map::Entry::Occupied(entry) = + self.cached_entity_bin_keys.entry(visible_entity) + { + if entry.get().change_tick == current_change_tick { + self.valid_cached_entity_bin_keys.insert(entry.index()); + return true; + } + } + + false + } + + /// Removes all entities not marked as clean from the bins. + /// + /// During `queue_material_meshes`, we process all visible entities and mark + /// each as clean as we come to it. Then, in [`sweep_old_entities`], we call + /// this method, which removes entities that aren't marked as clean from the + /// bins. + pub fn sweep_old_entities(&mut self) { + // Search for entities not marked as valid. We have to do this in + // reverse order because `swap_remove_index` will potentially invalidate + // all indices after the one we remove. + for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) { + let Some((entity, entity_bin_key)) = + self.cached_entity_bin_keys.swap_remove_index(index) + else { + continue; + }; + + remove_entity_from_bin( + entity, + &entity_bin_key, + &mut self.multidrawable_meshes, + &mut self.batchable_meshes, + &mut self.unbatchable_meshes, + &mut self.non_mesh_items, + ); + } + + // If an entity changed bins, we need to remove it from its old bin. + for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) { + remove_entity_from_bin( + entity_that_changed_bins.main_entity, + &entity_that_changed_bins.old_bin_key, + &mut self.multidrawable_meshes, + &mut self.batchable_meshes, + &mut self.unbatchable_meshes, + &mut self.non_mesh_items, + ); + } + } +} + +/// Removes an entity from a bin. +/// +/// If this makes the bin empty, this function removes the bin as well. +/// +/// This is a standalone function instead of a method on [`BinnedRenderPhase`] +/// for borrow check reasons. +fn remove_entity_from_bin( + entity: MainEntity, + entity_bin_key: &CachedBinKey, + 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>, +) where + BPI: BinnedPhaseItem, +{ + match entity_bin_key.phase_type { + BinnedRenderPhaseType::MultidrawableMesh => { + if let indexmap::map::Entry::Occupied(mut batch_set_entry) = + multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone()) + { + if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry + .get_mut() + .entry(entity_bin_key.bin_key.clone()) + { + bin_entry.get_mut().remove(entity); + + // If the bin is now empty, remove the bin. + if bin_entry.get_mut().is_empty() { + bin_entry.swap_remove(); + } + } + + // If the batch set is now empty, remove it. This will perturb + // the order, but that's OK because we're going to sort the bin + // afterwards. + if batch_set_entry.get_mut().is_empty() { + batch_set_entry.swap_remove(); + } + } + } + + BinnedRenderPhaseType::BatchableMesh => { + if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry(( + entity_bin_key.batch_set_key.clone(), + entity_bin_key.bin_key.clone(), + )) { + bin_entry.get_mut().remove(entity); + + // If the bin is now empty, remove the bin. + if bin_entry.get_mut().is_empty() { + bin_entry.swap_remove(); + } + } + } + + BinnedRenderPhaseType::UnbatchableMesh => { + if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry(( + entity_bin_key.batch_set_key.clone(), + entity_bin_key.bin_key.clone(), + )) { + bin_entry.get_mut().entities.remove(&entity); + + // If the bin is now empty, remove the bin. + if bin_entry.get_mut().entities.is_empty() { + bin_entry.swap_remove(); + } + } + } + + BinnedRenderPhaseType::NonMesh => { + if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry(( + entity_bin_key.batch_set_key.clone(), + entity_bin_key.bin_key.clone(), + )) { + bin_entry.get_mut().remove(entity); + + // If the bin is now empty, remove the bin. + if bin_entry.get_mut().is_empty() { + bin_entry.swap_remove(); + } + } + } } } @@ -699,13 +948,10 @@ where { fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self { Self { - multidrawable_mesh_keys: vec![], - multidrawable_mesh_values: HashMap::default(), - batchable_mesh_keys: vec![], - batchable_mesh_values: HashMap::default(), - unbatchable_mesh_keys: vec![], - unbatchable_mesh_values: HashMap::default(), - non_mesh_items: vec![], + multidrawable_meshes: IndexMap::default(), + batchable_meshes: IndexMap::default(), + unbatchable_meshes: IndexMap::default(), + non_mesh_items: IndexMap::default(), batch_sets: match gpu_preprocessing { GpuPreprocessingMode::Culling => { BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![]) @@ -715,6 +961,9 @@ where } GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]), }, + cached_entity_bin_keys: IndexMap::default(), + valid_cached_entity_bin_keys: FixedBitSet::new(), + entities_that_changed_bins: vec![], } } } @@ -766,18 +1015,26 @@ impl UnbatchableBinnedEntityIndexSet { /// /// This is the version used when the pipeline supports GPU preprocessing: e.g. /// 3D PBR meshes. -pub struct BinnedRenderPhasePlugin(PhantomData<(BPI, GFBD)>) +pub struct BinnedRenderPhasePlugin where BPI: BinnedPhaseItem, - GFBD: GetFullBatchData; + GFBD: GetFullBatchData, +{ + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, + phantom: PhantomData<(BPI, GFBD)>, +} -impl Default for BinnedRenderPhasePlugin +impl BinnedRenderPhasePlugin where BPI: BinnedPhaseItem, GFBD: GetFullBatchData, { - fn default() -> Self { - Self(PhantomData) + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { + debug_flags, + phantom: PhantomData, + } } } @@ -793,6 +1050,11 @@ where render_app .init_resource::>() + .init_resource::>() + .insert_resource(PhaseIndirectParametersBuffers::::new( + self.debug_flags + .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), + )) .add_systems( Render, ( @@ -808,6 +1070,14 @@ where ), ) .in_set(RenderSet::PrepareResources), + sweep_old_entities::.in_set(RenderSet::QueueSweep), + gpu_preprocessing::collect_buffers_for_phase:: + .run_if( + resource_exists::< + BatchedInstanceBuffers, + >, + ) + .in_set(RenderSet::PrepareResourcesCollectPhaseBuffers), ), ); } @@ -851,18 +1121,26 @@ where /// /// This is the version used when the pipeline supports GPU preprocessing: e.g. /// 3D PBR meshes. -pub struct SortedRenderPhasePlugin(PhantomData<(SPI, GFBD)>) +pub struct SortedRenderPhasePlugin where SPI: SortedPhaseItem, - GFBD: GetFullBatchData; + GFBD: GetFullBatchData, +{ + /// Debugging flags that can optionally be set when constructing the renderer. + pub debug_flags: RenderDebugFlags, + phantom: PhantomData<(SPI, GFBD)>, +} -impl Default for SortedRenderPhasePlugin +impl SortedRenderPhasePlugin where SPI: SortedPhaseItem, GFBD: GetFullBatchData, { - fn default() -> Self { - Self(PhantomData) + pub fn new(debug_flags: RenderDebugFlags) -> Self { + Self { + debug_flags, + phantom: PhantomData, + } } } @@ -878,18 +1156,33 @@ where render_app .init_resource::>() + .init_resource::>() + .insert_resource(PhaseIndirectParametersBuffers::::new( + self.debug_flags + .contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS), + )) .add_systems( Render, ( - no_gpu_preprocessing::batch_and_prepare_sorted_render_phase:: - .run_if(resource_exists::>), - gpu_preprocessing::batch_and_prepare_sorted_render_phase::.run_if( - resource_exists::< - BatchedInstanceBuffers, - >, - ), - ) - .in_set(RenderSet::PrepareResources), + ( + no_gpu_preprocessing::batch_and_prepare_sorted_render_phase:: + .run_if(resource_exists::>), + gpu_preprocessing::batch_and_prepare_sorted_render_phase:: + .run_if( + resource_exists::< + BatchedInstanceBuffers, + >, + ), + ) + .in_set(RenderSet::PrepareResources), + gpu_preprocessing::collect_buffers_for_phase:: + .run_if( + resource_exists::< + BatchedInstanceBuffers, + >, + ) + .in_set(RenderSet::PrepareResourcesCollectPhaseBuffers), + ), ); } } @@ -1345,6 +1638,18 @@ where } } +/// Removes entities that became invisible or changed phases from the bins. +/// +/// This must run after queuing. +pub fn sweep_old_entities(mut render_phases: ResMut>) +where + BPI: BinnedPhaseItem, +{ + for phase in render_phases.0.values_mut() { + phase.sweep_old_entities(); + } +} + impl BinnedRenderPhaseType { pub fn mesh( batchable: bool, @@ -1357,3 +1662,128 @@ impl BinnedRenderPhaseType { } } } + +impl RenderBin { + /// Creates a [`RenderBin`] containing a single entity. + fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin { + let mut entities = IndexMap::default(); + entities.insert(entity, uniform_index); + RenderBin { entities } + } + + /// Inserts an entity into the bin. + fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) { + self.entities.insert(entity, uniform_index); + } + + /// Removes an entity from the bin. + fn remove(&mut self, entity_to_remove: MainEntity) { + self.entities.swap_remove(&entity_to_remove); + } + + /// Returns true if the bin contains no entities. + fn is_empty(&self) -> bool { + self.entities.is_empty() + } + + /// Returns the [`IndexMap`] containing all the entities in the bin, along + /// with the cached [`InputUniformIndex`] of each. + #[inline] + pub fn entities(&self) -> &IndexMap { + &self.entities + } +} + +/// An iterator that efficiently finds the indices of all zero bits in a +/// [`FixedBitSet`] and returns them in reverse order. +/// +/// [`FixedBitSet`] doesn't natively offer this functionality, so we have to +/// implement it ourselves. +#[derive(Debug)] +struct ReverseFixedBitSetZeroesIterator<'a> { + /// The bit set. + bitset: &'a FixedBitSet, + /// The next bit index we're going to scan when [`Iterator::next`] is + /// called. + bit_index: isize, +} + +impl<'a> ReverseFixedBitSetZeroesIterator<'a> { + fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> { + ReverseFixedBitSetZeroesIterator { + bitset, + bit_index: (bitset.len() as isize) - 1, + } + } +} + +impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> { + type Item = usize; + + fn next(&mut self) -> Option { + while self.bit_index >= 0 { + // Unpack the bit index into block and bit. + let block_index = self.bit_index / (Block::BITS as isize); + let bit_pos = self.bit_index % (Block::BITS as isize); + + // Grab the block. Mask off all bits above the one we're scanning + // from by setting them all to 1. + let mut block = self.bitset.as_slice()[block_index as usize]; + if bit_pos + 1 < (Block::BITS as isize) { + block |= (!0) << (bit_pos + 1); + } + + // Search for the next unset bit. Note that the `leading_ones` + // function counts from the MSB to the LSB, so we need to flip it to + // get the bit number. + let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1; + + // If we found an unset bit, return it. + if pos != -1 { + let result = block_index * (Block::BITS as isize) + pos; + self.bit_index = result - 1; + return Some(result as usize); + } + + // Otherwise, go to the previous block. + self.bit_index = block_index * (Block::BITS as isize) - 1; + } + + None + } +} + +#[cfg(test)] +mod test { + use super::ReverseFixedBitSetZeroesIterator; + use fixedbitset::FixedBitSet; + use proptest::{collection::vec, prop_assert_eq, proptest}; + + proptest! { + #[test] + fn reverse_fixed_bit_set_zeroes_iterator( + bits in vec(0usize..1024usize, 0usize..1024usize), + size in 0usize..1024usize, + ) { + // Build a random bit set. + let mut bitset = FixedBitSet::new(); + bitset.grow(size); + for bit in bits { + if bit < size { + bitset.set(bit, true); + } + } + + // Iterate over the bit set backwards in a naive way, and check that + // that iteration sequence corresponds to the optimized one. + let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset); + for bit_index in (0..size).rev() { + if !bitset.contains(bit_index) { + prop_assert_eq!(iter.next(), Some(bit_index)); + } + } + + prop_assert_eq!(iter.next(), None); + } + } +} diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index c1c99181a4d51..9fe835bb1b8ab 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -6,14 +6,17 @@ use crate::{ renderer::RenderDevice, texture::GpuImage, }; -use alloc::sync::Arc; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::system::{SystemParam, SystemParamItem}; pub use bevy_render_macros::AsBindGroup; use core::ops::Deref; use encase::ShaderType; use thiserror::Error; -use wgpu::{BindGroupEntry, BindGroupLayoutEntry, BindingResource, TextureViewDimension}; +use wgpu::{ + BindGroupEntry, BindGroupLayoutEntry, BindingResource, SamplerBindingType, TextureViewDimension, +}; + +use super::{BindlessDescriptor, BindlessSlabResourceLimit}; define_atomic_id!(BindGroupId); @@ -29,7 +32,7 @@ define_atomic_id!(BindGroupId); #[derive(Clone, Debug)] pub struct BindGroup { id: BindGroupId, - value: Arc>, + value: WgpuWrapper, } impl BindGroup { @@ -58,7 +61,7 @@ impl From for BindGroup { fn from(value: wgpu::BindGroup) -> Self { BindGroup { id: BindGroupId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } @@ -260,12 +263,33 @@ impl Deref for BindGroup { /// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes: /// ## `uniform(BINDING_INDEX, ConvertedShaderType)` /// -/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a uniform, much -/// like the field-level `uniform` attribute. The difference is that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`, -/// which must implement [`ShaderType`], instead of a specific field implementing [`ShaderType`]. This is useful if more complicated conversion -/// logic is required. The conversion is done using the [`AsBindGroupShaderType`] trait, which is automatically implemented -/// if `&Self` implements [`Into`]. Only use [`AsBindGroupShaderType`] if access to resources like [`RenderAssets`] is -/// required. +/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a +/// uniform, much like the field-level `uniform` attribute. The difference is +/// that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`, +/// which must implement [`ShaderType`], instead of a specific field +/// implementing [`ShaderType`]. This is useful if more complicated conversion +/// logic is required, or when using bindless mode (see below). The conversion +/// is done using the [`AsBindGroupShaderType`] trait, +/// which is automatically implemented if `&Self` implements +/// [`Into`]. Outside of bindless mode, only use +/// [`AsBindGroupShaderType`] if access to resources like +/// [`RenderAssets`] is required. +/// +/// * In bindless mode (see `bindless(COUNT)`), this attribute becomes +/// `uniform(BINDLESS_INDEX, ConvertedShaderType, +/// binding_array(BINDING_INDEX))`. The resulting uniform buffers will be +/// available in the shader as a binding array at the given `BINDING_INDEX`. The +/// `BINDLESS_INDEX` specifies the offset of the buffer in the bindless index +/// table. +/// +/// For example, suppose that the material slot is stored in a variable named +/// `slot`, the bindless index table is named `material_indices`, and that the +/// first field (index 0) of the bindless index table type is named +/// `material`. Then specifying `#[uniform(0, StandardMaterialUniform, +/// binding_array(10)]` will create a binding array buffer declared in the +/// shader as `var material_array: +/// binding_array` and accessible as +/// `material_array[material_indices[slot].material]`. /// /// ## `bind_group_data(DataType)` /// @@ -274,30 +298,77 @@ impl Deref for BindGroup { /// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute /// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline). /// -/// ## `bindless(COUNT)` +/// ## `bindless` /// /// * This switch enables *bindless resources*, which changes the way Bevy -/// supplies resources (uniforms, textures, and samplers) to the shader. -/// When bindless resources are enabled, and the current platform supports -/// them, instead of presenting a single instance of a resource to your -/// shader Bevy will instead present a *binding array* of `COUNT` elements. -/// In your shader, the index of the element of each binding array -/// corresponding to the mesh currently being drawn can be retrieved with -/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot & -/// 0xffffu`. -/// * Bindless uniforms don't exist, so in bindless mode all uniforms and -/// uniform buffers are automatically replaced with read-only storage -/// buffers. -/// * 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. +/// supplies resources (textures, and samplers) to the shader. When bindless +/// resources are enabled, and the current platform supports them, Bevy will +/// allocate textures, and samplers into *binding arrays*, separated based on +/// type and will supply your shader with indices into those arrays. +/// * Bindless textures and samplers are placed into the appropriate global +/// array defined in `bevy_render::bindless` (`bindless.wgsl`). +/// * Bevy doesn't currently support bindless buffers, except for those created +/// with the `uniform(BINDLESS_INDEX, ConvertedShaderType, +/// binding_array(BINDING_INDEX))` attribute. If you need to include a buffer in +/// your object, and you can't create the data in that buffer with the `uniform` +/// attribute, consider a non-bindless object instead. /// * If bindless mode is enabled, the `BINDLESS` definition will be /// 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. +/// * 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 +/// `mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu`. +/// * You can limit the size of the bindless slabs to N resources with the +/// `limit(N)` declaration. For example, `#[bindless(limit(16))]` ensures that +/// 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 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. +/// * The following diagram illustrates how bindless mode works using a subset +/// of `StandardMaterial`: +/// +/// ```text +/// Shader Bindings Sampler Binding Array +/// +----+-----------------------------+ +-----------+-----------+-----+ +/// +---| 0 | material_indices | +->| sampler 0 | sampler 1 | ... | +/// | +----+-----------------------------+ | +-----------+-----------+-----+ +/// | | 1 | bindless_samplers_filtering +--+ ^ +/// | +----+-----------------------------+ +-------------------------------+ +/// | | .. | ... | | +/// | +----+-----------------------------+ Texture Binding Array | +/// | | 5 | bindless_textures_2d +--+ +-----------+-----------+-----+ | +/// | +----+-----------------------------+ +->| texture 0 | texture 1 | ... | | +/// | | .. | ... | +-----------+-----------+-----+ | +/// | +----+-----------------------------+ ^ | +/// | + 10 | material_array +--+ +---------------------------+ | +/// | +----+-----------------------------+ | | | +/// | | Buffer Binding Array | | +/// | | +----------+----------+-----+ | | +/// | +->| buffer 0 | buffer 1 | ... | | | +/// | Material Bindless Indices +----------+----------+-----+ | | +/// | +----+-----------------------------+ ^ | | +/// +-->| 0 | material +----------+ | | +/// +----+-----------------------------+ | | +/// | 1 | base_color_texture +---------------------------------------+ | +/// +----+-----------------------------+ | +/// | 2 | base_color_sampler +-------------------------------------------+ +/// +----+-----------------------------+ +/// | .. | ... | +/// +----+-----------------------------+ +/// ``` /// /// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can /// also be equivalently represented with a single struct-level uniform attribute: @@ -365,7 +436,7 @@ pub trait AsBindGroup { /// Note that the *actual* slot count may be different from this value, due /// to platform limitations. For example, if bindless resources aren't /// supported on this platform, the actual slot count will be 1. - fn bindless_slot_count() -> Option { + fn bindless_slot_count() -> Option { None } @@ -452,6 +523,10 @@ pub trait AsBindGroup { ) -> Vec where Self: Sized; + + fn bindless_descriptor() -> Option { + None + } } /// An error that occurs during [`AsBindGroup::as_bind_group`] calls. @@ -491,7 +566,7 @@ pub struct BindingResources(pub Vec<(u32, OwnedBindingResource)>); pub enum OwnedBindingResource { Buffer(Buffer), TextureView(TextureViewDimension, TextureView), - Sampler(Sampler), + Sampler(SamplerBindingType, Sampler), } impl OwnedBindingResource { @@ -499,7 +574,7 @@ impl OwnedBindingResource { match self { OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(), OwnedBindingResource::TextureView(_, view) => BindingResource::TextureView(view), - OwnedBindingResource::Sampler(sampler) => BindingResource::Sampler(sampler), + OwnedBindingResource::Sampler(_, sampler) => BindingResource::Sampler(sampler), } } } @@ -529,7 +604,6 @@ where #[cfg(test)] mod test { use super::*; - use crate as bevy_render; use bevy_asset::Handle; use bevy_image::Image; 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 7b3853d39c707..e19f5b969fc03 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -1,6 +1,5 @@ use crate::define_atomic_id; use crate::renderer::WgpuWrapper; -use alloc::sync::Arc; use core::ops::Deref; define_atomic_id!(BindGroupLayoutId); @@ -16,7 +15,7 @@ define_atomic_id!(BindGroupLayoutId); #[derive(Clone, Debug)] pub struct BindGroupLayout { id: BindGroupLayoutId, - value: Arc>, + value: WgpuWrapper, } impl PartialEq for BindGroupLayout { @@ -50,7 +49,7 @@ impl From for BindGroupLayout { fn from(value: wgpu::BindGroupLayout) -> Self { BindGroupLayout { id: BindGroupLayoutId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } diff --git a/crates/bevy_render/src/render_resource/bindless.rs b/crates/bevy_render/src/render_resource/bindless.rs new file mode 100644 index 0000000000000..d34de59e67fbb --- /dev/null +++ b/crates/bevy_render/src/render_resource/bindless.rs @@ -0,0 +1,313 @@ +//! Types and functions relating to bindless resources. + +use alloc::borrow::Cow; +use core::num::{NonZeroU32, NonZeroU64}; + +use bevy_derive::{Deref, DerefMut}; +use wgpu::{ + BindGroupLayoutEntry, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension, +}; + +use crate::render_resource::binding_types::storage_buffer_read_only_sized; + +use super::binding_types::{ + sampler, texture_1d, texture_2d, texture_2d_array, texture_3d, texture_cube, texture_cube_array, +}; + +/// The default value for the number of resources that can be stored in a slab +/// on this platform. +/// +/// See the documentation for [`BindlessSlabResourceLimit`] for more +/// information. +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 64; +/// The default value for the number of resources that can be stored in a slab +/// on this platform. +/// +/// See the documentation for [`BindlessSlabResourceLimit`] for more +/// information. +#[cfg(not(any(target_os = "macos", target_os = "ios")))] +pub const AUTO_BINDLESS_SLAB_RESOURCE_LIMIT: u32 = 2048; + +/// The binding numbers for the built-in binding arrays of each bindless +/// resource type. +/// +/// In the case of materials, the material allocator manages these binding +/// arrays. +/// +/// `bindless.wgsl` contains declarations of these arrays for use in your +/// shaders. If you change these, make sure to update that file as well. +pub static BINDING_NUMBERS: [(BindlessResourceType, BindingNumber); 9] = [ + (BindlessResourceType::SamplerFiltering, BindingNumber(1)), + (BindlessResourceType::SamplerNonFiltering, BindingNumber(2)), + (BindlessResourceType::SamplerComparison, BindingNumber(3)), + (BindlessResourceType::Texture1d, BindingNumber(4)), + (BindlessResourceType::Texture2d, BindingNumber(5)), + (BindlessResourceType::Texture2dArray, BindingNumber(6)), + (BindlessResourceType::Texture3d, BindingNumber(7)), + (BindlessResourceType::TextureCube, BindingNumber(8)), + (BindlessResourceType::TextureCubeArray, BindingNumber(9)), +]; + +/// The maximum number of resources that can be stored in a slab. +/// +/// This limit primarily exists in order to work around `wgpu` performance +/// problems involving large numbers of bindless resources. Also, some +/// platforms, such as Metal, currently enforce limits on the number of +/// resources in use. +/// +/// This corresponds to `LIMIT` in the `#[bindless(LIMIT)]` attribute when +/// deriving [`crate::render_resource::AsBindGroup`]. +#[derive(Clone, Copy, Default, PartialEq, Debug)] +pub enum BindlessSlabResourceLimit { + /// Allows the renderer to choose a reasonable value for the resource limit + /// based on the platform. + /// + /// This value has been tuned, so you should default to this value unless + /// you have special platform-specific considerations that prevent you from + /// using it. + #[default] + Auto, + + /// A custom value for the resource limit. + /// + /// Bevy will allocate no more than this number of resources in a slab, + /// unless exceeding this value is necessary in order to allocate at all + /// (i.e. unless the number of bindless resources in your bind group exceeds + /// this value), in which case Bevy can exceed it. + Custom(u32), +} + +/// Information about the bindless resources in this object. +/// +/// The material bind group allocator uses this descriptor in order to create +/// and maintain bind groups. The fields within this bindless descriptor are +/// [`Cow`]s in order to support both the common case in which the fields are +/// simply `static` constants and the more unusual case in which the fields are +/// dynamically generated efficiently. An example of the latter case is +/// `ExtendedMaterial`, which needs to assemble a bindless descriptor from those +/// of the base material and the material extension at runtime. +/// +/// This structure will only be present if this object is bindless. +pub struct BindlessDescriptor { + /// The bindless resource types that this object uses, in order of bindless + /// index. + /// + /// The resource assigned to binding index 0 will be at index 0, the + /// resource assigned to binding index will be at index 1 in this array, and + /// so on. Unused binding indices are set to [`BindlessResourceType::None`]. + pub resources: Cow<'static, [BindlessResourceType]>, + /// The [`BindlessBufferDescriptor`] for each bindless buffer that this + /// object uses. + /// + /// The order of this array is irrelevant. + pub buffers: Cow<'static, [BindlessBufferDescriptor]>, +} + +/// The type of potentially-bindless resource. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum BindlessResourceType { + /// No bindless resource. + /// + /// This is used as a placeholder to fill holes in the + /// [`BindlessDescriptor::resources`] list. + None, + /// A storage buffer. + Buffer, + /// A filtering sampler. + SamplerFiltering, + /// A non-filtering sampler (nearest neighbor). + SamplerNonFiltering, + /// A comparison sampler (typically used for shadow maps). + SamplerComparison, + /// A 1D texture. + Texture1d, + /// A 2D texture. + Texture2d, + /// A 2D texture array. + /// + /// Note that this differs from a binding array. 2D texture arrays must all + /// have the same size and format. + Texture2dArray, + /// A 3D texture. + Texture3d, + /// A cubemap texture. + TextureCube, + /// A cubemap texture array. + /// + /// Note that this differs from a binding array. Cubemap texture arrays must + /// all have the same size and format. + TextureCubeArray, +} + +/// Describes a bindless buffer. +/// +/// Unlike samplers and textures, each buffer in a bind group gets its own +/// unique bind group entry. That is, there isn't any `bindless_buffers` binding +/// array to go along with `bindless_textures_2d`, +/// `bindless_samplers_filtering`, etc. Therefore, this descriptor contains two +/// indices: the *binding number* and the *bindless index*. The binding number +/// is the `@binding` number used in the shader, while the bindless index is the +/// index of the buffer in the bindless index table (which is itself +/// conventionally bound to binding number 0). +/// +/// When declaring the buffer in a derived implementation +/// [`crate::render_resource::AsBindGroup`] with syntax like +/// `#[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)] +pub struct BindlessBufferDescriptor { + /// The actual binding number of the buffer. + /// + /// This is declared with `@binding` in WGSL. When deriving + /// [`crate::render_resource::AsBindGroup`], this is the `BINDING_NUMBER` in + /// `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, + /// bindless(BINDING_NUMBER)]`. + pub binding_number: BindingNumber, + /// The index of the buffer in the bindless index table. + /// + /// In the shader, this is the index into the table bound to binding 0. When + /// deriving [`crate::render_resource::AsBindGroup`], this is the + /// `BINDLESS_INDEX` in `#[uniform(BINDLESS_INDEX, StandardMaterialUniform, + /// bindless(BINDING_NUMBER)]`. + pub bindless_index: BindlessIndex, + /// The size of the buffer in bytes. + pub size: usize, +} + +/// The index of the actual binding in the bind group. +/// +/// This is the value specified in WGSL as `@binding`. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deref, DerefMut)] +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)] +pub struct BindlessIndex(pub u32); + +/// Creates the bind group layout entries common to all shaders that use +/// bindless bind groups. +/// +/// `bindless_resource_count` specifies the total number of bindless resources. +/// `bindless_slab_resource_limit` specifies the resolved +/// [`BindlessSlabResourceLimit`] value. +pub fn create_bindless_bind_group_layout_entries( + bindless_resource_count: u32, + bindless_slab_resource_limit: u32, +) -> Vec { + let bindless_slab_resource_limit = + NonZeroU32::new(bindless_slab_resource_limit).expect("Bindless slot count must be nonzero"); + + // The maximum size of a binding array is the + // `bindless_slab_resource_limit`, which would occur if all of the bindless + // resources were of the same type. So we create our binding arrays with + // that size. + + vec![ + // 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), + ) + .build(0, ShaderStages::all()), + // Continue with the common bindless buffers. + sampler(SamplerBindingType::Filtering) + .count(bindless_slab_resource_limit) + .build(1, ShaderStages::all()), + sampler(SamplerBindingType::NonFiltering) + .count(bindless_slab_resource_limit) + .build(2, ShaderStages::all()), + sampler(SamplerBindingType::Comparison) + .count(bindless_slab_resource_limit) + .build(3, ShaderStages::all()), + texture_1d(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(4, ShaderStages::all()), + texture_2d(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(5, ShaderStages::all()), + texture_2d_array(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(6, ShaderStages::all()), + texture_3d(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(7, ShaderStages::all()), + texture_cube(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(8, ShaderStages::all()), + texture_cube_array(TextureSampleType::Float { filterable: true }) + .count(bindless_slab_resource_limit) + .build(9, ShaderStages::all()), + ] +} + +impl BindlessSlabResourceLimit { + /// Determines the actual bindless slab resource limit on this platform. + pub fn resolve(&self) -> u32 { + match *self { + BindlessSlabResourceLimit::Auto => AUTO_BINDLESS_SLAB_RESOURCE_LIMIT, + BindlessSlabResourceLimit::Custom(limit) => limit, + } + } +} + +impl BindlessResourceType { + /// Returns the binding number for the common array of this resource type. + /// + /// For example, if you pass `BindlessResourceType::Texture2d`, this will + /// return 5, in order to match the `@group(2) @binding(5) var + /// bindless_textures_2d: binding_array>` declaration in + /// `bindless.wgsl`. + /// + /// Not all resource types have fixed binding numbers. If you call + /// [`Self::binding_number`] on such a resource type, it returns `None`. + /// + /// Note that this returns a static reference to the binding number, not the + /// binding number itself. This is to conform to an idiosyncratic API in + /// `wgpu` whereby binding numbers for binding arrays are taken by `&u32` + /// *reference*, not by `u32` value. + pub fn binding_number(&self) -> Option<&'static BindingNumber> { + match BINDING_NUMBERS.binary_search_by_key(self, |(key, _)| *key) { + Ok(binding_number) => Some(&BINDING_NUMBERS[binding_number].1), + Err(_) => None, + } + } +} + +impl From for BindlessResourceType { + fn from(texture_view_dimension: TextureViewDimension) -> Self { + match texture_view_dimension { + TextureViewDimension::D1 => BindlessResourceType::Texture1d, + TextureViewDimension::D2 => BindlessResourceType::Texture2d, + TextureViewDimension::D2Array => BindlessResourceType::Texture2dArray, + TextureViewDimension::Cube => BindlessResourceType::TextureCube, + TextureViewDimension::CubeArray => BindlessResourceType::TextureCubeArray, + TextureViewDimension::D3 => BindlessResourceType::Texture3d, + } + } +} + +impl From for BindlessResourceType { + fn from(sampler_binding_type: SamplerBindingType) -> Self { + match sampler_binding_type { + SamplerBindingType::Filtering => BindlessResourceType::SamplerFiltering, + SamplerBindingType::NonFiltering => BindlessResourceType::SamplerNonFiltering, + SamplerBindingType::Comparison => BindlessResourceType::SamplerComparison, + } + } +} + +impl From for BindlessIndex { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for BindingNumber { + fn from(value: u32) -> Self { + Self(value) + } +} diff --git a/crates/bevy_render/src/render_resource/buffer.rs b/crates/bevy_render/src/render_resource/buffer.rs index ea2e6b4c6838c..9b7bb2c41f487 100644 --- a/crates/bevy_render/src/render_resource/buffer.rs +++ b/crates/bevy_render/src/render_resource/buffer.rs @@ -1,6 +1,5 @@ use crate::define_atomic_id; use crate::renderer::WgpuWrapper; -use alloc::sync::Arc; use core::ops::{Bound, Deref, RangeBounds}; define_atomic_id!(BufferId); @@ -8,8 +7,7 @@ define_atomic_id!(BufferId); #[derive(Clone, Debug)] pub struct Buffer { id: BufferId, - value: Arc>, - size: wgpu::BufferAddress, + value: WgpuWrapper, } impl Buffer { @@ -28,7 +26,7 @@ impl Buffer { let size = match bounds.end_bound() { Bound::Included(&bound) => bound + 1, Bound::Excluded(&bound) => bound, - Bound::Unbounded => self.size, + Bound::Unbounded => self.value.size(), } - offset; BufferSlice { id: self.id, @@ -48,8 +46,7 @@ impl From for Buffer { fn from(value: wgpu::Buffer) -> Self { Buffer { id: BufferId::new(), - size: value.size(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 0feaa8487885b..2077024215463 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -103,6 +103,11 @@ impl RawBufferVec { self.values.append(&mut other.values); } + /// Returns the value at the given index. + pub fn get(&self, index: u32) -> Option<&T> { + self.values.get(index as usize) + } + /// Sets the value at the given index. /// /// The index must be less than [`RawBufferVec::len`]. @@ -451,8 +456,14 @@ where /// Reserves space for one more element in the buffer and returns its index. pub fn add(&mut self) -> usize { + self.add_multiple(1) + } + + /// Reserves space for the given number of elements in the buffer and + /// returns the index of the first one. + pub fn add_multiple(&mut self, count: usize) -> usize { let index = self.len; - self.len += 1; + self.len += count; index } diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 27a08851122c6..aab2fe5b6a798 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -3,6 +3,7 @@ mod bind_group; mod bind_group_entries; mod bind_group_layout; mod bind_group_layout_entries; +mod bindless; mod buffer; mod buffer_vec; mod gpu_array_buffer; @@ -19,6 +20,7 @@ pub use bind_group::*; pub use bind_group_entries::*; pub use bind_group_layout::*; pub use bind_group_layout_entries::*; +pub use bindless::*; pub use buffer::*; pub use buffer_vec::*; pub use gpu_array_buffer::*; @@ -43,19 +45,20 @@ pub use wgpu::{ ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass, ComputePassDescriptor, ComputePipelineDescriptor as RawComputePipelineDescriptor, DepthBiasState, DepthStencilState, DownlevelFlags, Extent3d, Face, Features as WgpuFeatures, - FilterMode, FragmentState as RawFragmentState, FrontFace, ImageCopyBuffer, ImageCopyBufferBase, - ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, ImageSubresourceRange, IndexFormat, + FilterMode, FragmentState as RawFragmentState, FrontFace, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, Maintain, MapMode, MultisampleState, Operations, Origin3d, PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, PushConstantRange, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor, Sampler as WgpuSampler, - SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, - ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, StoreOp, - TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, - TextureUsages, TextureView as WgpuTextureView, TextureViewDescriptor, TextureViewDimension, - VertexAttribute, VertexBufferLayout as RawVertexBufferLayout, VertexFormat, - VertexState as RawVertexState, VertexStepMode, COPY_BUFFER_ALIGNMENT, + SamplerBindingType, SamplerBindingType as WgpuSamplerBindingType, SamplerDescriptor, + 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, }; 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 be400a02e9b0f..30f9a974b853f 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -6,7 +6,6 @@ use crate::{ render_resource::{BindGroupLayout, Shader}, }; use alloc::borrow::Cow; -use alloc::sync::Arc; use bevy_asset::Handle; use core::ops::Deref; use wgpu::{ @@ -22,7 +21,7 @@ define_atomic_id!(RenderPipelineId); #[derive(Clone, Debug)] pub struct RenderPipeline { id: RenderPipelineId, - value: Arc>, + value: WgpuWrapper, } impl RenderPipeline { @@ -36,7 +35,7 @@ impl From for RenderPipeline { fn from(value: wgpu::RenderPipeline) -> Self { RenderPipeline { id: RenderPipelineId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } @@ -59,7 +58,7 @@ define_atomic_id!(ComputePipelineId); #[derive(Clone, Debug)] pub struct ComputePipeline { id: ComputePipelineId, - value: Arc>, + value: WgpuWrapper, } impl ComputePipeline { @@ -74,7 +73,7 @@ impl From for ComputePipeline { fn from(value: wgpu::ComputePipeline) -> Self { ComputePipeline { id: ComputePipelineId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 207c401024837..fb82a495d2c62 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -251,7 +251,7 @@ impl ShaderCache { shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); } - if cfg!(feature = "ios_simulator") { + if cfg!(target_abi = "sim") { shader_defs.push("NO_CUBE_ARRAY_TEXTURES_SUPPORT".into()); } @@ -318,7 +318,19 @@ impl ShaderCache { render_device .wgpu_device() .push_error_scope(wgpu::ErrorFilter::Validation); - let shader_module = render_device.create_shader_module(module_descriptor); + + let shader_module = match shader.validate_shader { + ValidateShader::Enabled => { + render_device.create_and_validate_shader_module(module_descriptor) + } + // SAFETY: we are interfacing with shader code, which may contain undefined behavior, + // such as indexing out of bounds. + // The checks required are prohibitively expensive and a poor default for game engines. + ValidateShader::Disabled => unsafe { + render_device.create_shader_module(module_descriptor) + }, + }; + let error = render_device.wgpu_device().pop_error_scope(); // `now_or_never` will return Some if the future is ready and None otherwise. @@ -1069,6 +1081,18 @@ fn get_capabilities(features: Features, downlevel: DownlevelFlags) -> Capabiliti Capabilities::SUBGROUP_VERTEX_STAGE, features.contains(Features::SUBGROUP_VERTEX), ); + capabilities.set( + Capabilities::SHADER_FLOAT32_ATOMIC, + features.contains(Features::SHADER_FLOAT32_ATOMIC), + ); + capabilities.set( + Capabilities::TEXTURE_ATOMIC, + features.contains(Features::TEXTURE_ATOMIC), + ); + capabilities.set( + Capabilities::TEXTURE_INT64_ATOMIC, + features.contains(Features::TEXTURE_INT64_ATOMIC), + ); capabilities } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 36f71ed3bf611..e1d00ec0d7f30 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -21,6 +21,30 @@ pub enum ShaderReflectError { #[error(transparent)] Validation(#[from] naga::WithSpan), } + +/// Describes whether or not to perform runtime checks on shaders. +/// Runtime checks can be enabled for safety at the cost of speed. +/// By default no runtime checks will be performed. +/// +/// # Panics +/// Because no runtime checks are performed for spirv, +/// enabling `ValidateShader` for spirv will cause a panic +#[derive(Clone, Debug, Default)] +pub enum ValidateShader { + #[default] + /// No runtime checks for soundness (e.g. bound checking) are performed. + /// + /// This is suitable for trusted shaders, written by your program or dependencies you trust. + Disabled, + /// Enable's runtime checks for soundness (e.g. bound checking). + /// + /// While this can have a meaningful impact on performance, + /// this setting should *always* be enabled when loading untrusted shaders. + /// This might occur if you are creating a shader playground, running user-generated shaders + /// (as in `VRChat`), or writing a web browser in Bevy. + Enabled, +} + /// A shader, as defined by its [`ShaderSource`](wgpu::ShaderSource) and [`ShaderStage`](naga::ShaderStage) /// This is an "unprocessed" shader. It can contain preprocessor directives. #[derive(Asset, TypePath, Debug, Clone)] @@ -36,6 +60,10 @@ pub struct Shader { // we must store strong handles to our dependencies to stop them // from being immediately dropped if we are the only user. pub file_dependencies: Vec>, + /// Enable or disable runtime shader validation, trading safety against speed. + /// + /// Please read the [`ValidateShader`] docs for a discussion of the tradeoffs involved. + pub validate_shader: ValidateShader, } impl Shader { @@ -78,6 +106,7 @@ impl Shader { additional_imports: Default::default(), shader_defs: Default::default(), file_dependencies: Default::default(), + validate_shader: ValidateShader::Disabled, } } @@ -108,6 +137,7 @@ impl Shader { additional_imports: Default::default(), shader_defs: Default::default(), file_dependencies: Default::default(), + validate_shader: ValidateShader::Disabled, } } @@ -121,6 +151,7 @@ impl Shader { additional_imports: Default::default(), shader_defs: Default::default(), file_dependencies: Default::default(), + validate_shader: ValidateShader::Disabled, } } diff --git a/crates/bevy_render/src/render_resource/texture.rs b/crates/bevy_render/src/render_resource/texture.rs index 4ebcf0373a026..f975fc18f31e6 100644 --- a/crates/bevy_render/src/render_resource/texture.rs +++ b/crates/bevy_render/src/render_resource/texture.rs @@ -1,6 +1,5 @@ use crate::define_atomic_id; use crate::renderer::WgpuWrapper; -use alloc::sync::Arc; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::resource::Resource; use core::ops::Deref; @@ -23,7 +22,7 @@ define_atomic_id!(TextureId); #[derive(Clone, Debug)] pub struct Texture { id: TextureId, - value: Arc>, + value: WgpuWrapper, } impl Texture { @@ -43,7 +42,7 @@ impl From for Texture { fn from(value: wgpu::Texture) -> Self { Texture { id: TextureId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } @@ -63,18 +62,16 @@ define_atomic_id!(TextureViewId); #[derive(Clone, Debug)] pub struct TextureView { id: TextureViewId, - value: Arc>, + value: WgpuWrapper, } pub struct SurfaceTexture { - value: Arc>, + value: WgpuWrapper, } impl SurfaceTexture { - pub fn try_unwrap(self) -> Option { - Arc::try_unwrap(self.value) - .map(WgpuWrapper::into_inner) - .ok() + pub fn present(self) { + self.value.into_inner().present(); } } @@ -90,7 +87,7 @@ impl From for TextureView { fn from(value: wgpu::TextureView) -> Self { TextureView { id: TextureViewId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } @@ -98,7 +95,7 @@ impl From for TextureView { impl From for SurfaceTexture { fn from(value: wgpu::SurfaceTexture) -> Self { SurfaceTexture { - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } @@ -131,7 +128,7 @@ define_atomic_id!(SamplerId); #[derive(Clone, Debug)] pub struct Sampler { id: SamplerId, - value: Arc>, + value: WgpuWrapper, } impl Sampler { @@ -146,7 +143,7 @@ impl From for Sampler { fn from(value: wgpu::Sampler) -> Self { Sampler { id: SamplerId::new(), - value: Arc::new(WgpuWrapper::new(value)), + value: WgpuWrapper::new(value), } } } diff --git a/crates/bevy_render/src/render_resource/uniform_buffer.rs b/crates/bevy_render/src/render_resource/uniform_buffer.rs index 48c2b4a62ad08..b7d22972df469 100644 --- a/crates/bevy_render/src/render_resource/uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/uniform_buffer.rs @@ -278,11 +278,11 @@ impl DynamicUniformBuffer { device: &RenderDevice, queue: &'a RenderQueue, ) -> Option> { - let alignment = if cfg!(feature = "ios_simulator") { + let alignment = if cfg!(target_abi = "sim") { // On iOS simulator on silicon macs, metal validation check that the host OS alignment // is respected, but the device reports the correct value for iOS, which is smaller. // Use the larger value. - // See https://github.com/bevyengine/bevy/pull/10178 - remove if it's not needed anymore. + // See https://github.com/gfx-rs/wgpu/issues/7057 - remove if it's not needed anymore. AlignmentValue::new(256) } else { AlignmentValue::new(device.limits().min_uniform_buffer_offset_alignment as u64) diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 0f46506801ed8..5845234191f63 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -87,14 +87,12 @@ pub fn render_system(world: &mut World, state: &mut SystemState(); for window in windows.values_mut() { - if let Some(wrapped_texture) = window.swap_chain_texture.take() { - if let Some(surface_texture) = wrapped_texture.try_unwrap() { - // TODO(clean): winit docs recommends calling pre_present_notify before this. - // though `present()` doesn't present the frame, it schedules it to be presented - // by wgpu. - // https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify - surface_texture.present(); - } + if let Some(surface_texture) = window.swap_chain_texture.take() { + // TODO(clean): winit docs recommends calling pre_present_notify before this. + // though `present()` doesn't present the frame, it schedules it to be presented + // by wgpu. + // https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify + surface_texture.present(); } } @@ -224,10 +222,8 @@ pub async fn initialize_renderer( // RAY_QUERY and RAY_TRACING_ACCELERATION STRUCTURE will sometimes cause DeviceLost failures on platforms // that report them as supported: // - // WGPU also currently doesn't actually support these features yet, so we should disable - // them until they are safe to enable. - features -= wgpu::Features::RAY_QUERY; - features -= wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE; + features -= wgpu::Features::EXPERIMENTAL_RAY_QUERY; + features -= wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE; limits = adapter.limits(); } diff --git a/crates/bevy_render/src/renderer/render_device.rs b/crates/bevy_render/src/renderer/render_device.rs index 97ce7e34856cc..d33139745baf8 100644 --- a/crates/bevy_render/src/renderer/render_device.rs +++ b/crates/bevy_render/src/renderer/render_device.rs @@ -4,7 +4,6 @@ use crate::render_resource::{ RenderPipeline, Sampler, Texture, }; use crate::WgpuWrapper; -use alloc::sync::Arc; use bevy_ecs::resource::Resource; use wgpu::{ util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, @@ -14,17 +13,17 @@ use wgpu::{ /// This GPU device is responsible for the creation of most rendering and compute resources. #[derive(Resource, Clone)] pub struct RenderDevice { - device: Arc>, + device: WgpuWrapper, } impl From for RenderDevice { fn from(device: wgpu::Device) -> Self { - Self::new(Arc::new(WgpuWrapper::new(device))) + Self::new(WgpuWrapper::new(device)) } } impl RenderDevice { - pub fn new(device: Arc>) -> Self { + pub fn new(device: WgpuWrapper) -> Self { Self { device } } @@ -45,8 +44,18 @@ impl RenderDevice { } /// Creates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code. + /// + /// # Safety + /// + /// Creates a shader module with user-customizable runtime checks which allows shaders to + /// perform operations which can lead to undefined behavior like indexing out of bounds, + /// To avoid UB, ensure any unchecked shaders are sound! + /// This method should never be called for user-supplied shaders. #[inline] - pub fn create_shader_module(&self, desc: wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule { + pub unsafe fn create_shader_module( + &self, + desc: wgpu::ShaderModuleDescriptor, + ) -> wgpu::ShaderModule { #[cfg(feature = "spirv_shader_passthrough")] match &desc.source { wgpu::ShaderSource::SpirV(source) @@ -65,9 +74,36 @@ impl RenderDevice { }) } } - _ => self.device.create_shader_module(desc), + // SAFETY: + // + // This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior. + // No attempt is made to ensure that data is valid SPIR-V. + _ => unsafe { + self.device + .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) + }, } + #[cfg(not(feature = "spirv_shader_passthrough"))] + // SAFETY: the caller is responsible for upholding the safety requirements + unsafe { + self.device + .create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) + } + } + /// Creates and validates a [`ShaderModule`](wgpu::ShaderModule) from either SPIR-V or WGSL source code. + /// + /// See [`ValidateShader`](bevy_render::render_resource::ValidateShader) for more information on the tradeoffs involved with shader validation. + #[inline] + pub fn create_and_validate_shader_module( + &self, + desc: wgpu::ShaderModuleDescriptor, + ) -> wgpu::ShaderModule { + #[cfg(feature = "spirv_shader_passthrough")] + match &desc.source { + wgpu::ShaderSource::SpirV(_source) => panic!("no safety checks are performed for spirv shaders. use `create_shader_module` instead"), + _ => self.device.create_shader_module(desc), + } #[cfg(not(feature = "spirv_shader_passthrough"))] self.device.create_shader_module(desc) } diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index 1ceaf81357523..d4eb9b7680cfa 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -71,10 +71,10 @@ impl Default for WgpuSettings { Backends::all() }; - let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(default_backends)); + let backends = Some(Backends::from_env().unwrap_or(default_backends)); let power_preference = - wgpu::util::power_preference_from_env().unwrap_or(PowerPreference::HighPerformance); + PowerPreference::from_env().unwrap_or(PowerPreference::HighPerformance); let priority = settings_priority_from_env().unwrap_or(WgpuSettingsPriority::Functionality); @@ -100,13 +100,27 @@ impl Default for WgpuSettings { limits }; - let dx12_compiler = - wgpu::util::dx12_shader_compiler_from_env().unwrap_or(Dx12Compiler::Dxc { - dxil_path: None, - dxc_path: None, + let dx12_shader_compiler = + Dx12Compiler::from_env().unwrap_or(if cfg!(feature = "statically-linked-dxc") { + Dx12Compiler::StaticDxc + } else { + let dxc = "dxcompiler.dll"; + let dxil = "dxil.dll"; + + if cfg!(target_os = "windows") + && std::fs::metadata(dxc).is_ok() + && std::fs::metadata(dxil).is_ok() + { + Dx12Compiler::DynamicDxc { + dxc_path: String::from(dxc), + dxil_path: String::from(dxil), + } + } else { + Dx12Compiler::Fxc + } }); - let gles3_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); + let gles3_minor_version = Gles3MinorVersion::from_env().unwrap_or_default(); let instance_flags = InstanceFlags::default().with_env(); @@ -119,7 +133,7 @@ impl Default for WgpuSettings { disabled_features: None, limits, constrained_limits: None, - dx12_shader_compiler: dx12_compiler, + dx12_shader_compiler, gles3_minor_version, instance_flags, memory_hints: MemoryHints::default(), diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 60f57ba3a63c1..3c9dc57ff8f40 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -153,7 +153,7 @@ unsafe impl TrustedEntityBorrow 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)] +#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct MainEntity(Entity); impl MainEntity { #[inline] @@ -285,14 +285,9 @@ mod render_entities_world_query_impls { /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for RenderEntity { - type Item<'w> = Entity; type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; type State = <&'static RenderEntity as WorldQuery>::State; - fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>( fetch: Self::Fetch<'wlong>, ) -> Self::Fetch<'wshort> { @@ -337,18 +332,6 @@ mod render_entities_world_query_impls { unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) } } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - let component = - unsafe { <&RenderEntity as WorldQuery>::fetch(fetch, entity, table_row) }; - component.id() - } - fn update_component_access( &component_id: &ComponentId, access: &mut FilteredAccess, @@ -376,6 +359,23 @@ mod render_entities_world_query_impls { // Self::ReadOnly matches exactly the same archetypes/tables as Self. unsafe impl QueryData for RenderEntity { type ReadOnly = RenderEntity; + type Item<'w> = Entity; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + let component = + unsafe { <&RenderEntity as QueryData>::fetch(fetch, entity, table_row) }; + component.id() + } } // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. @@ -384,14 +384,9 @@ mod render_entities_world_query_impls { /// SAFETY: defers completely to `&RenderEntity` implementation, /// and then only modifies the output safely. unsafe impl WorldQuery for MainEntity { - type Item<'w> = Entity; type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; type State = <&'static MainEntity as WorldQuery>::State; - fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { - item - } - fn shrink_fetch<'wlong: 'wshort, 'wshort>( fetch: Self::Fetch<'wlong>, ) -> Self::Fetch<'wshort> { @@ -436,17 +431,6 @@ mod render_entities_world_query_impls { unsafe { <&MainEntity as WorldQuery>::set_table(fetch, &component_id, table) } } - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. - let component = unsafe { <&MainEntity as WorldQuery>::fetch(fetch, entity, table_row) }; - component.id() - } - fn update_component_access( &component_id: &ComponentId, access: &mut FilteredAccess, @@ -474,6 +458,22 @@ mod render_entities_world_query_impls { // Self::ReadOnly matches exactly the same archetypes/tables as Self. unsafe impl QueryData for MainEntity { type ReadOnly = MainEntity; + type Item<'w> = Entity; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity { + item + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + // SAFETY: defers to the `&T` implementation, with T set to `MainEntity`. + let component = unsafe { <&MainEntity as QueryData>::fetch(fetch, entity, table_row) }; + component.id() + } } // SAFETY: the underlying `Entity` is copied, and no mutable access is provided. diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 38d94ff5d77e1..277f9d477fcd7 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -98,7 +98,7 @@ fn fallback_image_new( RenderAssetUsages::RENDER_WORLD, ) } else { - let mut image = Image::default(); + let mut image = Image::default_uninit(); image.texture_descriptor.dimension = TextureDimension::D2; image.texture_descriptor.size = extents; image.texture_descriptor.format = format; @@ -114,7 +114,7 @@ fn fallback_image_new( render_queue, &image.texture_descriptor, TextureDataOrder::default(), - &image.data, + &image.data.expect("Image has no data"), ) } else { render_device.create_texture(&image.texture_descriptor) diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index f1ee1ade7e8f5..551bd3ee02e09 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -36,7 +36,7 @@ impl RenderAsset for GpuImage { #[inline] fn byte_len(image: &Self::SourceAsset) -> Option { - Some(image.data.len()) + image.data.as_ref().map(Vec::len) } /// Converts the extracted image into a [`GpuImage`]. @@ -45,13 +45,17 @@ impl RenderAsset for GpuImage { _: AssetId, (render_device, render_queue, default_sampler): &mut SystemParamItem, ) -> Result> { - let texture = render_device.create_texture_with_data( - render_queue, - &image.texture_descriptor, - // TODO: Is this correct? Do we need to use `MipMajor` if it's a ktx2 file? - wgpu::util::TextureDataOrder::default(), - &image.data, - ); + let texture = if let Some(ref data) = image.data { + render_device.create_texture_with_data( + render_queue, + &image.texture_descriptor, + // TODO: Is this correct? Do we need to use `MipMajor` if it's a ktx2 file? + wgpu::util::TextureDataOrder::default(), + data, + ) + } else { + render_device.create_texture(&image.texture_descriptor) + }; let texture_view = texture.create_view( image diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 3671b7a0c83db..6955de7ff4fc2 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -18,7 +18,7 @@ use crate::{ render_asset::RenderAssetPlugin, renderer::RenderDevice, Render, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; -use bevy_asset::{AssetApp, Assets, Handle}; +use bevy_asset::{weak_handle, AssetApp, Assets, Handle}; use bevy_ecs::prelude::*; /// A handle to a 1 x 1 transparent white image. @@ -27,7 +27,7 @@ use bevy_ecs::prelude::*; /// While that handle points to an opaque white 1 x 1 image, this handle points to a transparent 1 x 1 white image. // Number randomly selected by fair WolframAlpha query. Totally arbitrary. pub const TRANSPARENT_IMAGE_HANDLE: Handle = - Handle::weak_from_u128(154728948001857810431816125397303024160); + weak_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46"); // TODO: replace Texture names with Image names? /// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU. diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index a3a5118c30cca..ba0f8cca62ced 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -1,7 +1,8 @@ pub mod visibility; pub mod window; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; +use bevy_diagnostic::FrameCount; pub use visibility::*; pub use window::*; @@ -45,7 +46,7 @@ use wgpu::{ TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }; -pub const VIEW_TYPE_HANDLE: Handle = Handle::weak_from_u128(15421373904451797197); +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. /// @@ -186,6 +187,16 @@ impl Msaa { pub fn samples(&self) -> u32 { *self as u32 } + + pub fn from_samples(samples: u32) -> Self { + match samples { + 1 => Msaa::Off, + 2 => Msaa::Sample2, + 4 => Msaa::Sample4, + 8 => Msaa::Sample8, + _ => panic!("Unsupported MSAA sample count: {}", samples), + } + } } /// An identifier for a view that is stable across frames. @@ -558,6 +569,7 @@ pub struct ViewUniform { pub frustum: [Vec4; 6], pub color_grading: ColorGradingUniform, pub mip_bias: f32, + pub frame_count: u32, } #[derive(Resource)] @@ -879,6 +891,7 @@ pub fn prepare_view_uniforms( Option<&TemporalJitter>, Option<&MipBias>, )>, + frame_count: Res, ) { let view_iter = views.iter(); let view_count = view_iter.len(); @@ -932,6 +945,7 @@ pub fn prepare_view_uniforms( frustum, color_grading: extracted_view.color_grading.clone().into(), mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0, + frame_count: frame_count.0, }), }; diff --git a/crates/bevy_render/src/view/view.wgsl b/crates/bevy_render/src/view/view.wgsl index ed08599758a07..317de2eb88073 100644 --- a/crates/bevy_render/src/view/view.wgsl +++ b/crates/bevy_render/src/view/view.wgsl @@ -60,4 +60,5 @@ struct View { frustum: array, 6>, color_grading: ColorGrading, mip_bias: f32, + frame_count: u32, }; diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 33c6639266f15..09dc516b6ea9b 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -325,6 +325,10 @@ pub enum VisibilitySystems { /// the order of systems within this set is irrelevant, as [`check_visibility`] /// assumes that its operations are irreversible during the frame. CheckVisibility, + /// Label for the `mark_newly_hidden_entities_invisible` system, which sets + /// [`ViewVisibility`] to [`ViewVisibility::HIDDEN`] for entities that no + /// view has marked as visible. + MarkNewlyHiddenEntitiesInvisible, } pub struct VisibilityPlugin; @@ -340,6 +344,10 @@ impl Plugin for VisibilityPlugin { .before(CheckVisibility) .after(TransformSystem::TransformPropagate), ) + .configure_sets( + PostUpdate, + MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility), + ) .init_resource::() .add_systems( PostUpdate, @@ -348,6 +356,7 @@ impl Plugin for VisibilityPlugin { (visibility_propagate_system, reset_view_visibility) .in_set(VisibilityPropagate), check_visibility.in_set(CheckVisibility), + mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible), ), ); } @@ -456,6 +465,10 @@ fn propagate_recursive( } /// Stores all entities that were visible in the previous frame. +/// +/// As systems that check visibility judge entities visible, they remove them +/// from this set. Afterward, the `mark_newly_hidden_entities_invisible` system +/// runs and marks every mesh still remaining in this set as hidden. #[derive(Resource, Default, Deref, DerefMut)] pub struct PreviousVisibleEntities(EntityHashSet); @@ -607,13 +620,23 @@ pub fn check_visibility( } } } +} - // Now whatever previous visible entities are left are entities that were +/// Marks any entities that weren't judged visible this frame as invisible. +/// +/// As visibility-determining systems run, they remove entities that they judge +/// visible from [`PreviousVisibleEntities`]. At the end of visibility +/// determination, all entities that remain in [`PreviousVisibleEntities`] must +/// be invisible. This system goes through those entities and marks them newly +/// invisible (which sets the change flag for them). +fn mark_newly_hidden_entities_invisible( + mut view_visibilities: Query<&mut ViewVisibility>, + mut previous_visible_entities: ResMut, +) { + // Whatever previous visible entities are left are entities that were // visible last frame but just became invisible. for entity in previous_visible_entities.drain() { - if let Ok((_, _, mut view_visibility, _, _, _, _, _, _)) = - visible_aabb_query.get_mut(entity) - { + if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) { *view_visibility = ViewVisibility::HIDDEN; } } diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index 05994d9cf57b0..d8a309036edb4 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -17,7 +17,7 @@ use crate::{ }; use alloc::{borrow::Cow, sync::Arc}; use bevy_app::{First, Plugin, Update}; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::hash_map::EntityHashMap, event::event_update_system, prelude::*, system::SystemState, @@ -392,7 +392,8 @@ fn prepare_screenshot_state( pub struct ScreenshotPlugin; -const SCREENSHOT_SHADER_HANDLE: Handle = Handle::weak_from_u128(11918575842344596158); +const SCREENSHOT_SHADER_HANDLE: Handle = + weak_handle!("c31753d6-326a-47cb-a359-65c97a471fda"); impl Plugin for ScreenshotPlugin { fn build(&self, app: &mut bevy_app::App) { @@ -592,7 +593,7 @@ fn render_screenshot( }; encoder.copy_texture_to_buffer( prepared_state.texture.as_image_copy(), - wgpu::ImageCopyBuffer { + wgpu::TexelCopyBufferInfo { buffer: &prepared_state.buffer, layout: gpu_readback::layout_data(extent, texture_format), }, diff --git a/crates/bevy_scene/LICENSE-APACHE b/crates/bevy_scene/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_scene/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_scene/LICENSE-MIT b/crates/bevy_scene/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_scene/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_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index a40053ed5f28c..be45b54a811a9 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,15 +1,16 @@ use crate::{ron, DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; -use bevy_ecs::reflect::ReflectResource; +use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource}; use bevy_ecs::{ entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, - reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities}, + reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; use bevy_reflect::{PartialReflect, TypePath, TypeRegistry}; #[cfg(feature = "serialize")] use crate::serde::SceneSerializer; +use bevy_ecs::component::ComponentCloneBehavior; #[cfg(feature = "serialize")] use serde::Serialize; @@ -85,7 +86,7 @@ impl DynamicScene { // Apply/ add each component to the given entity. for component in &scene_entity.components { - let mut component = component.clone_value(); + let component = component.clone_value(); let type_info = component.get_represented_type_info().ok_or_else(|| { SceneSpawnError::NoRepresentedType { type_path: component.reflect_type_path().to_string(), @@ -103,19 +104,27 @@ 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::() { - SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(component.as_partial_reflect_mut(), mapper); - }); + { + let component_id = reflect_component.register_component(world); + // SAFETY: we registered the component above. the info exists + #[expect(unsafe_code, reason = "this is faster")] + let component_info = + unsafe { world.components().get_info_unchecked(component_id) }; + match component_info.clone_behavior() { + ComponentCloneBehavior::Ignore + | ComponentCloneBehavior::RelationshipTarget(_) => continue, + _ => {} + } } - reflect_component.apply_or_insert( - &mut world.entity_mut(entity), - component.as_partial_reflect(), - &type_registry, - ); + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(entity), + component.as_partial_reflect(), + &type_registry, + mapper, + ); + }); } } @@ -340,13 +349,13 @@ mod tests { #[reflect(Component)] struct A; - #[derive(Component, Reflect, VisitEntities)] - #[reflect(Component, MapEntities)] + #[derive(Component, Reflect)] + #[reflect(Component)] struct B(pub Entity); impl MapEntities for B { fn map_entities(&mut self, entity_mapper: &mut M) { - self.0 = entity_mapper.map_entity(self.0); + 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 e2981ed973048..c33346373bcc7 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -351,6 +351,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// [`deny_resource`]: Self::deny_resource #[must_use] pub fn extract_resources(mut self) -> Self { + // Don't extract the DefaultQueryFilters resource let original_world_dqf_id = self .original_world .components() diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 507f18f63a069..f7d52e6b62eaa 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![forbid(unsafe_code)] #![doc( html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" @@ -82,7 +81,7 @@ impl Plugin for ScenePlugin { if let Some(instance_ids) = scene_spawner.spawned_dynamic_scenes.get_mut(&id) { instance_ids.remove(&scene_instance); } - scene_spawner.despawn_instance(scene_instance); + scene_spawner.unregister_instance(scene_instance); } }); @@ -96,7 +95,7 @@ impl Plugin for ScenePlugin { let Some(mut scene_spawner) = world.get_resource_mut::() else { return; }; - scene_spawner.despawn_instance(scene_instance); + scene_spawner.unregister_instance(scene_instance); } }); } diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 7d74ce8cb25e3..9040b15e85f7c 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -3,9 +3,10 @@ use core::any::TypeId; use crate::{DynamicScene, SceneSpawnError}; use bevy_asset::Asset; use bevy_ecs::{ + component::ComponentCloneBehavior, entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, - reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, world::World, }; use bevy_reflect::{PartialReflect, TypePath}; @@ -123,6 +124,12 @@ impl Scene { .get_info(component_id) .expect("component_ids in archetypes should have ComponentInfo"); + match component_info.clone_behavior() { + ComponentCloneBehavior::Ignore + | ComponentCloneBehavior::RelationshipTarget(_) => continue, + _ => {} + } + let registration = type_registry .get(component_info.type_id().unwrap()) .ok_or_else(|| SceneSpawnError::UnregisteredType { @@ -135,7 +142,7 @@ impl Scene { } })?; - let Some(mut component) = reflect_component + let Some(component) = reflect_component .reflect(self.world.entity(scene_entity.id())) .map(PartialReflect::clone_value) else { @@ -144,16 +151,14 @@ impl Scene { // If this component references entities in the scene, // update them to the entities in the world. - if let Some(map_entities) = registration.data::() { - SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(component.as_partial_reflect_mut(), mapper); - }); - } - reflect_component.apply_or_insert( - &mut world.entity_mut(entity), - component.as_partial_reflect(), - &type_registry, - ); + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(entity), + component.as_partial_reflect(), + &type_registry, + mapper, + ); + }); } } } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 3d4014f123534..cf8195e676d37 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -179,10 +179,19 @@ impl SceneSpawner { } /// Schedule the despawn of a scene instance, removing all its entities from the world. + /// + /// Note: this will despawn _all_ entities associated with this instance, including those + /// that have been removed from the scene hierarchy. To despawn _only_ entities still in the hierarchy, + /// despawn the relevant root entity directly. pub fn despawn_instance(&mut self, instance_id: InstanceId) { self.instances_to_despawn.push(instance_id); } + /// This will remove all records of this instance, without despawning any entities. + pub fn unregister_instance(&mut self, instance_id: InstanceId) { + self.spawned_instances.remove(&instance_id); + } + /// Immediately despawns all instances of a dynamic scene. pub fn despawn_sync( &mut self, diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index ede9076ea2325..8e26863e792ee 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -515,10 +515,10 @@ mod tests { DynamicScene, DynamicSceneBuilder, }; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, Entity, VisitEntities, VisitEntitiesMut}, + entity::{hash_map::EntityHashMap, Entity}, prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, - reflect::{AppTypeRegistry, ReflectMapEntities}, + reflect::AppTypeRegistry, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -584,9 +584,9 @@ mod tests { foo: i32, } - #[derive(Clone, Component, Reflect, PartialEq, VisitEntities, VisitEntitiesMut)] - #[reflect(Component, MapEntities, PartialEq)] - struct MyEntityRef(Entity); + #[derive(Clone, Component, Reflect, PartialEq)] + #[reflect(Component, PartialEq)] + struct MyEntityRef(#[entities] Entity); impl FromWorld for MyEntityRef { fn from_world(_world: &mut World) -> Self { diff --git a/crates/bevy_sprite/LICENSE-APACHE b/crates/bevy_sprite/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_sprite/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_sprite/LICENSE-MIT b/crates/bevy_sprite/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_sprite/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_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2d7936f75d3f0..568701725d605 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -37,11 +37,12 @@ pub use sprite::*; pub use texture_slice::*; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, Assets, Handle}; -use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_asset::{load_internal_asset, weak_handle, AssetEvents, Assets, Handle}; +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, mesh::{Mesh, Mesh2d, MeshAabb}, primitives::Aabb, render_phase::AddRenderCommand, @@ -74,9 +75,10 @@ impl Default for SpritePlugin { } } -pub const SPRITE_SHADER_HANDLE: Handle = Handle::weak_from_u128(2763343953151597127); +pub const SPRITE_SHADER_HANDLE: Handle = + weak_handle!("ed996613-54c0-49bd-81be-1c2d1a0d03c2"); pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle = - Handle::weak_from_u128(8846920112458963210); + weak_handle!("43947210-8df6-459a-8f2a-12f350d174cc"); /// System set for sprite rendering. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] @@ -115,7 +117,7 @@ impl Plugin for SpritePlugin { ( calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds), ( - compute_slices_on_asset_event, + compute_slices_on_asset_event.before(AssetEvents), compute_slices_on_sprite_change, ) .in_set(SpriteSystem::ComputeSlices), @@ -150,6 +152,8 @@ impl Plugin for SpritePlugin { .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), ), ); }; diff --git a/crates/bevy_sprite/src/mesh2d/color_material.rs b/crates/bevy_sprite/src/mesh2d/color_material.rs index e844fc3a997a8..15c965b7a788d 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.rs +++ b/crates/bevy_sprite/src/mesh2d/color_material.rs @@ -1,6 +1,6 @@ use crate::{AlphaMode2d, Material2d, Material2dPlugin}; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle}; use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba}; use bevy_image::Image; use bevy_math::Vec4; @@ -8,7 +8,7 @@ use bevy_reflect::prelude::*; use bevy_render::{render_asset::RenderAssets, render_resource::*, texture::GpuImage}; pub const COLOR_MATERIAL_SHADER_HANDLE: Handle = - Handle::weak_from_u128(3253086872234592509); + weak_handle!("92e0e6e9-ed0b-4db3-89ab-5f65d3678250"); #[derive(Default)] pub struct ColorMaterialPlugin; diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index e44b27a005fa3..fbe2c2132cdb6 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,22 +1,28 @@ use crate::{ DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, - SetMesh2dBindGroup, SetMesh2dViewBindGroup, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, ViewKeyCache, ViewSpecializationTicks, }; -use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle}; +use bevy_app::{App, Plugin, PostUpdate}; +use bevy_asset::prelude::AssetChanged; +use bevy_asset::{AsAssetId, Asset, AssetApp, AssetEvents, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ core_2d::{ AlphaMask2d, AlphaMask2dBinKey, BatchSetKey2d, Opaque2d, Opaque2dBinKey, Transparent2d, }, - tonemapping::{DebandDither, Tonemapping}, + tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_math::FloatOrd; +use bevy_platform_support::collections::HashMap; use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use bevy_render::render_phase::{DrawFunctionId, InputUniformIndex}; +use bevy_render::render_resource::CachedRenderPipelineId; use bevy_render::view::RenderVisibleEntities; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, @@ -35,7 +41,7 @@ use bevy_render::{ }, renderer::RenderDevice, sync_world::{MainEntity, MainEntityHashMap}, - view::{ExtractedView, Msaa, ViewVisibility}, + view::{ExtractedView, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use core::{hash::Hash, marker::PhantomData}; @@ -202,6 +208,14 @@ impl From<&MeshMaterial2d> for AssetId { } } +impl AsAssetId for MeshMaterial2d { + type Asset = M; + + fn as_asset_id(&self) -> AssetId { + self.id() + } +} + /// Sets how a 2d material's base color alpha channel is used for transparency. /// Currently, this only works with [`Mesh2d`]. Sprites are always transparent. /// @@ -244,22 +258,41 @@ where { fn build(&self, app: &mut App) { app.init_asset::() + .init_resource::>() .register_type::>() - .add_plugins(RenderAssetPlugin::>::default()); + .add_plugins(RenderAssetPlugin::>::default()) + .add_systems( + PostUpdate, + check_entities_needing_specialization::.after(AssetEvents), + ); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_mesh_materials_2d::) + .add_systems( + ExtractSchedule, + ( + extract_entities_needs_specialization::, + extract_mesh_materials_2d::, + ), + ) .add_systems( Render, - queue_material2d_meshes:: - .in_set(RenderSet::QueueMeshes) - .after(prepare_assets::>), + ( + specialize_material2d_meshes:: + .in_set(RenderSet::PrepareMeshes) + .after(prepare_assets::>) + .after(prepare_assets::), + queue_material2d_meshes:: + .in_set(RenderSet::QueueMeshes) + .after(prepare_assets::>), + ), ); } } @@ -511,10 +544,109 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin } } -pub fn queue_material2d_meshes( - opaque_draw_functions: Res>, - alpha_mask_draw_functions: Res>, - transparent_draw_functions: Res>, +pub fn extract_entities_needs_specialization( + entities_needing_specialization: Extract>>, + mut entity_specialization_ticks: ResMut>, + ticks: SystemChangeTick, +) where + M: Material2d, +{ + 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()); + } +} + +#[derive(Clone, Resource, Deref, DerefMut, Debug)] +pub struct EntitiesNeedingSpecialization { + #[deref] + pub entities: Vec, + _marker: PhantomData, +} + +impl Default for EntitiesNeedingSpecialization { + fn default() -> Self { + Self { + entities: Default::default(), + _marker: Default::default(), + } + } +} + +#[derive(Clone, Resource, Deref, DerefMut, Debug)] +pub struct EntitySpecializationTicks { + #[deref] + pub entities: MainEntityHashMap, + _marker: PhantomData, +} + +impl Default for EntitySpecializationTicks { + fn default() -> Self { + Self { + entities: MainEntityHashMap::default(), + _marker: Default::default(), + } + } +} + +/// Stores the [`SpecializedMaterial2dViewPipelineCache`] for each view. +#[derive(Resource, Deref, DerefMut)] +pub struct SpecializedMaterial2dPipelineCache { + // view_entity -> view pipeline cache + #[deref] + map: MainEntityHashMap>, + marker: PhantomData, +} + +/// 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)] +pub struct SpecializedMaterial2dViewPipelineCache { + // material entity -> (tick, pipeline_id) + #[deref] + map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>, + marker: PhantomData, +} + +impl Default for SpecializedMaterial2dPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +impl Default for SpecializedMaterial2dViewPipelineCache { + fn default() -> Self { + Self { + map: HashMap::default(), + marker: PhantomData, + } + } +} + +pub fn check_entities_needing_specialization( + needs_specialization: Query< + Entity, + Or<( + Changed, + AssetChanged, + Changed>, + AssetChanged>, + )>, + >, + mut entities_needing_specialization: ResMut>, +) where + M: Material2d, +{ + entities_needing_specialization.clear(); + for entity in &needs_specialization { + entities_needing_specialization.push(entity); + } +} + +pub fn specialize_material2d_meshes( material2d_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, @@ -524,16 +656,15 @@ pub fn queue_material2d_meshes( ), mut render_mesh_instances: ResMut, render_material_instances: Res>, - mut transparent_render_phases: ResMut>, - mut opaque_render_phases: ResMut>, - mut alpha_mask_render_phases: ResMut>, - views: Query<( - &ExtractedView, - &RenderVisibleEntities, - &Msaa, - Option<&Tonemapping>, - Option<&DebandDither>, - )>, + transparent_render_phases: Res>, + opaque_render_phases: Res>, + alpha_mask_render_phases: Res>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + view_key_cache: Res, + entity_specialization_ticks: Res>, + view_specialization_ticks: Res, + ticks: SystemChangeTick, + mut specialized_material_pipeline_cache: ResMut>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -541,36 +672,36 @@ pub fn queue_material2d_meshes( return; } - for (view, visible_entities, msaa, tonemapping, dither) in &views { - let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) - else { + for (view_entity, view, visible_entities) in &views { + if !transparent_render_phases.contains_key(&view.retained_view_entity) + && !opaque_render_phases.contains_key(&view.retained_view_entity) + && !alpha_mask_render_phases.contains_key(&view.retained_view_entity) + { continue; - }; - let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { - continue; - }; - let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity) - else { + } + + let Some(view_key) = view_key_cache.get(view_entity) else { continue; }; - let draw_transparent_2d = transparent_draw_functions.read().id::>(); - let draw_opaque_2d = opaque_draw_functions.read().id::>(); - let draw_alpha_mask_2d = alpha_mask_draw_functions.read().id::>(); - - let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) - | Mesh2dPipelineKey::from_hdr(view.hdr); - - if !view.hdr { - if let Some(tonemapping) = tonemapping { - view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= Mesh2dPipelineKey::DEBAND_DITHER; + let view_tick = view_specialization_ticks.get(view_entity).unwrap(); + let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache + .entry(*view_entity) + .or_default(); + + for (_, visible_entity) in visible_entities.iter::() { + 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; } - } - for (render_entity, visible_entity) in visible_entities.iter::() { + let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -583,7 +714,7 @@ pub fn queue_material2d_meshes( let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let mesh_key = view_key + let mesh_key = *view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology()) | material_2d.properties.mesh_pipeline_key_bits; @@ -605,6 +736,78 @@ pub fn queue_material2d_meshes( } }; + view_specialized_material_pipeline_cache + .insert(*visible_entity, (ticks.this_run(), pipeline_id)); + } + } +} + +pub fn queue_material2d_meshes( + (render_meshes, render_materials): ( + Res>, + Res>>, + ), + mut render_mesh_instances: ResMut, + render_material_instances: Res>, + mut transparent_render_phases: ResMut>, + mut opaque_render_phases: ResMut>, + mut alpha_mask_render_phases: ResMut>, + views: Query<(&MainEntity, &ExtractedView, &RenderVisibleEntities)>, + specialized_material_pipeline_cache: ResMut>, +) where + M::Data: PartialEq + Eq + Hash + Clone, +{ + if render_material_instances.is_empty() { + return; + } + + for (view_entity, view, visible_entities) in &views { + let Some(view_specialized_material_pipeline_cache) = + specialized_material_pipeline_cache.get(view_entity) + else { + continue; + }; + + let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) + else { + continue; + }; + let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else { + continue; + }; + let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view.retained_view_entity) + else { + continue; + }; + + for (render_entity, visible_entity) in visible_entities.iter::() { + 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 opaque_phase.validate_cached_entity(*visible_entity, current_change_tick) + || alpha_mask_phase.validate_cached_entity(*visible_entity, current_change_tick) + { + 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; + }; + let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + mesh_instance.material_bind_group_id = material_2d.get_bind_group_id(); let mesh_z = mesh_instance.transforms.world_from_local.translation.z; @@ -623,7 +826,7 @@ pub fn queue_material2d_meshes( AlphaMode2d::Opaque => { let bin_key = Opaque2dBinKey { pipeline: pipeline_id, - draw_function: draw_opaque_2d, + draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; @@ -633,13 +836,15 @@ pub fn queue_material2d_meshes( }, bin_key, (*render_entity, *visible_entity), + InputUniformIndex::default(), binned_render_phase_type, + current_change_tick, ); } AlphaMode2d::Mask(_) => { let bin_key = AlphaMask2dBinKey { pipeline: pipeline_id, - draw_function: draw_alpha_mask_2d, + draw_function: material_2d.properties.draw_function_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; @@ -649,13 +854,15 @@ pub fn queue_material2d_meshes( }, bin_key, (*render_entity, *visible_entity), + InputUniformIndex::default(), binned_render_phase_type, + current_change_tick, ); } AlphaMode2d::Blend => { transparent_phase.add(Transparent2d { entity: (*render_entity, *visible_entity), - draw_function: draw_transparent_2d, + draw_function: material_2d.properties.draw_function_id, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the // lowest sort key and getting closer should increase. As we have @@ -689,6 +896,7 @@ pub struct Material2dProperties { /// These are precalculated so that we can just "or" them together in /// [`queue_material2d_meshes`]. pub mesh_pipeline_key_bits: Mesh2dPipelineKey, + pub draw_function_id: DrawFunctionId, } /// Data prepared for a [`Material2d`] instance. @@ -708,17 +916,42 @@ impl PreparedMaterial2d { impl RenderAsset for PreparedMaterial2d { type SourceAsset = M; - type Param = (SRes, SRes>, M::Param); + type Param = ( + SRes, + SRes>, + SRes>, + SRes>, + SRes>, + M::Param, + ); fn prepare_asset( material: Self::SourceAsset, _: AssetId, - (render_device, pipeline, material_param): &mut SystemParamItem, + ( + render_device, + pipeline, + opaque_draw_functions, + alpha_mask_draw_functions, + transparent_draw_functions, + material_param, + ): &mut SystemParamItem, ) -> Result> { match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) { Ok(prepared) => { let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty(); mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode())); + + let draw_function_id = match material.alpha_mode() { + AlphaMode2d::Opaque => opaque_draw_functions.read().id::>(), + AlphaMode2d::Mask(_) => { + alpha_mask_draw_functions.read().id::>() + } + AlphaMode2d::Blend => { + transparent_draw_functions.read().id::>() + } + }; + Ok(PreparedMaterial2d { bindings: prepared.bindings, bind_group: prepared.bind_group, @@ -727,6 +960,7 @@ impl RenderAsset for PreparedMaterial2d { depth_bias: material.depth_bias(), alpha_mode: material.alpha_mode(), mesh_pipeline_key_bits, + draw_function_id, }, }) } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 86d02d5680b8b..5822d47ed6231 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,7 +1,8 @@ use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, AssetId, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle}; -use crate::Material2dBindGroupId; +use crate::{tonemapping_pipeline_key, Material2dBindGroupId}; +use bevy_core_pipeline::tonemapping::DebandDither; use bevy_core_pipeline::{ core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT}, tonemapping::{ @@ -9,6 +10,8 @@ use bevy_core_pipeline::{ }, }; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::component::Tick; +use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ prelude::*, query::ROQueryItem, @@ -16,9 +19,12 @@ use bevy_ecs::{ }; 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::{ batching::{ - gpu_preprocessing::IndirectParametersMetadata, + gpu_preprocessing::IndirectParametersCpuMetadata, no_gpu_preprocessing::{ self, batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, BatchedInstanceBuffer, @@ -32,7 +38,8 @@ use bevy_render::{ }, render_asset::RenderAssets, render_phase::{ - PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, TrackedRenderPass, + sweep_old_entities, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, + TrackedRenderPass, }, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, @@ -50,13 +57,20 @@ use tracing::error; #[derive(Default)] pub struct Mesh2dRenderPlugin; -pub const MESH2D_VERTEX_OUTPUT: Handle = Handle::weak_from_u128(7646632476603252194); -pub const MESH2D_VIEW_TYPES_HANDLE: Handle = Handle::weak_from_u128(12677582416765805110); -pub const MESH2D_VIEW_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(6901431444735842434); -pub const MESH2D_TYPES_HANDLE: Handle = Handle::weak_from_u128(8994673400261890424); -pub const MESH2D_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(8983617858458862856); -pub const MESH2D_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(4976379308250389413); -pub const MESH2D_SHADER_HANDLE: Handle = Handle::weak_from_u128(2971387252468633715); +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) { @@ -94,12 +108,18 @@ impl Plugin for Mesh2dRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::() .init_resource::() .init_resource::>() .add_systems(ExtractSchedule, extract_mesh2d) .add_systems( Render, ( + ( + sweep_old_entities::, + sweep_old_entities::, + ) + .in_set(RenderSet::QueueSweep), batch_and_prepare_binned_render_phase:: .in_set(RenderSet::PrepareResources), batch_and_prepare_binned_render_phase:: @@ -137,7 +157,13 @@ impl Plugin for Mesh2dRenderPlugin { render_app .insert_resource(batched_instance_buffer) - .init_resource::(); + .init_resource::() + .init_resource::() + .init_resource::() + .add_systems( + Render, + check_views_need_specialization.in_set(PrepareAssets), + ); } // Load the mesh_bindings shader module here as it depends on runtime information about @@ -152,6 +178,48 @@ impl Plugin for Mesh2dRenderPlugin { } } +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewKeyCache(MainEntityHashMap); + +#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)] +pub struct ViewSpecializationTicks(MainEntityHashMap); + +pub fn check_views_need_specialization( + mut view_key_cache: ResMut, + mut view_specialization_ticks: ResMut, + views: Query<( + &MainEntity, + &ExtractedView, + &Msaa, + Option<&Tonemapping>, + Option<&DebandDither>, + )>, + ticks: SystemChangeTick, +) { + for (view_entity, view, msaa, tonemapping, dither) in &views { + let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) + | Mesh2dPipelineKey::from_hdr(view.hdr); + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; + view_key |= tonemapping_pipeline_key(*tonemapping); + } + if let Some(DebandDither::Enabled) = dither { + view_key |= Mesh2dPipelineKey::DEBAND_DITHER; + } + } + + if !view_key_cache + .get_mut(view_entity) + .is_some_and(|current_key| *current_key == view_key) + { + view_key_cache.insert(*view_entity, view_key); + view_specialization_ticks.insert(*view_entity, ticks.this_run()); + } + } +} + #[derive(Component)] pub struct Mesh2dTransforms { pub world_from_local: Affine3, @@ -169,10 +237,11 @@ pub struct Mesh2dUniform { pub local_from_world_transpose_a: [Vec4; 2], pub local_from_world_transpose_b: f32, pub flags: u32, + pub tag: u32, } -impl From<&Mesh2dTransforms> for Mesh2dUniform { - fn from(mesh_transforms: &Mesh2dTransforms) -> Self { +impl Mesh2dUniform { + fn from_components(mesh_transforms: &Mesh2dTransforms, tag: u32) -> Self { let (local_from_world_transpose_a, local_from_world_transpose_b) = mesh_transforms.world_from_local.inverse_transpose_3x3(); Self { @@ -180,6 +249,7 @@ impl From<&Mesh2dTransforms> for Mesh2dUniform { local_from_world_transpose_a, local_from_world_transpose_b, flags: mesh_transforms.flags, + tag, } } } @@ -198,6 +268,7 @@ pub struct RenderMesh2dInstance { pub mesh_asset_id: AssetId, pub material_bind_group_id: Material2dBindGroupId, pub automatic_batching: bool, + pub tag: u32, } #[derive(Default, Resource, Deref, DerefMut)] @@ -214,13 +285,14 @@ pub fn extract_mesh2d( &ViewVisibility, &GlobalTransform, &Mesh2d, + Option<&MeshTag>, Has, )>, >, ) { render_mesh_instances.clear(); - for (entity, view_visibility, transform, handle, no_automatic_batching) in &query { + for (entity, view_visibility, transform, handle, tag, no_automatic_batching) in &query { if !view_visibility.get() { continue; } @@ -234,6 +306,7 @@ pub fn extract_mesh2d( mesh_asset_id: handle.0.id(), material_bind_group_id: Material2dBindGroupId::default(), automatic_batching: !no_automatic_batching, + tag: tag.map_or(0, |i| **i), }, ); } @@ -298,8 +371,8 @@ impl FromWorld for Mesh2dPipeline { let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( texture.as_image_copy(), - &image.data, - ImageDataLayout { + image.data.as_ref().expect("Image has no data"), + TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(image.width() * format_size as u32), rows_per_image: None, @@ -361,7 +434,7 @@ impl GetBatchData for Mesh2dPipeline { ) -> Option<(Self::BufferData, Option)> { let mesh_instance = mesh_instances.get(&main_entity)?; Some(( - (&mesh_instance.transforms).into(), + Mesh2dUniform::from_components(&mesh_instance.transforms, mesh_instance.tag), mesh_instance.automatic_batching.then_some(( mesh_instance.material_bind_group_id, mesh_instance.mesh_asset_id, @@ -378,7 +451,10 @@ impl GetFullBatchData for Mesh2dPipeline { main_entity: MainEntity, ) -> Option { let mesh_instance = mesh_instances.get(&main_entity)?; - Some((&mesh_instance.transforms).into()) + Some(Mesh2dUniform::from_components( + &mesh_instance.transforms, + mesh_instance.tag, + )) } fn get_index_and_compare_data( @@ -404,32 +480,31 @@ impl GetFullBatchData for Mesh2dPipeline { } fn write_batch_indirect_parameters_metadata( - input_index: u32, indexed: bool, base_output_index: u32, batch_set_index: Option, - indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffers, + indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::UntypedPhaseIndirectParametersBuffers, indirect_parameters_offset: u32, ) { // Note that `IndirectParameters` covers both of these structures, even // though they actually have distinct layouts. See the comment above that // type for more information. - let indirect_parameters = IndirectParametersMetadata { - mesh_index: input_index, + let indirect_parameters = IndirectParametersCpuMetadata { base_output_index, batch_set_index: match batch_set_index { None => !0, Some(batch_set_index) => u32::from(batch_set_index), }, - early_instance_count: 0, - late_instance_count: 0, }; if indexed { - indirect_parameters_buffer.set_indexed(indirect_parameters_offset, indirect_parameters); + indirect_parameters_buffer + .indexed + .set(indirect_parameters_offset, indirect_parameters); } else { indirect_parameters_buffer - .set_non_indexed(indirect_parameters_offset, indirect_parameters); + .non_indexed + .set(indirect_parameters_offset, indirect_parameters); } } } diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index ca4a4eed67fac..4547f83d9b6af 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -1,6 +1,6 @@ use crate::{Material2d, Material2dKey, Material2dPlugin, Mesh2d}; use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; +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}; @@ -11,7 +11,8 @@ use bevy_render::{ use super::MeshMaterial2d; -pub const WIREFRAME_2D_SHADER_HANDLE: Handle = Handle::weak_from_u128(6920362697190520314); +pub const WIREFRAME_2D_SHADER_HANDLE: Handle = + weak_handle!("3d8a3853-2927-4de2-9dc7-3971e7e40970"); /// A [`Plugin`] that draws wireframes for 2D meshes. /// diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 5517629d322fb..480617a3f3fb2 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -1,6 +1,11 @@ //! 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. +//! +//! ## 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 bevy_app::prelude::*; @@ -241,6 +246,6 @@ fn sprite_picking( .collect(); let order = camera.order as f32; - output.send(PointerHits::new(*pointer, picks, order)); + output.write(PointerHits::new(*pointer, picks, order)); } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a2919d7860b14..b188ebbc1aa47 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -102,8 +102,8 @@ impl FromWorld for SpritePipeline { let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( texture.as_image_copy(), - &image.data, - ImageDataLayout { + image.data.as_ref().expect("Image has no data"), + TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(image.width() * format_size as u32), rows_per_image: None, diff --git a/crates/bevy_state/LICENSE-APACHE b/crates/bevy_state/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_state/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_state/LICENSE-MIT b/crates/bevy_state/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_state/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_state/macros/LICENSE-APACHE b/crates/bevy_state/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_state/macros/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_state/macros/LICENSE-MIT b/crates/bevy_state/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_state/macros/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_state/src/app.rs b/crates/bevy_state/src/app.rs index b00c60de53738..2419efd4d8a99 100644 --- a/crates/bevy_state/src/app.rs +++ b/crates/bevy_state/src/app.rs @@ -316,7 +316,6 @@ impl Plugin for StatesPlugin { #[cfg(test)] mod tests { use crate::{ - self as bevy_state, app::StatesPlugin, state::{State, StateTransition, StateTransitionEvent}, }; diff --git a/crates/bevy_state/src/condition.rs b/crates/bevy_state/src/condition.rs index f0a619708776d..1d14d9da070cd 100644 --- a/crates/bevy_state/src/condition.rs +++ b/crates/bevy_state/src/condition.rs @@ -171,8 +171,6 @@ pub fn state_changed(current_state: Option>>) -> bool { #[cfg(test)] mod tests { - use crate as bevy_state; - use bevy_ecs::schedule::{Condition, IntoSystemConfigs, Schedule}; use crate::prelude::*; diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index f25553747552f..b2714b50c5025 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -43,6 +43,9 @@ extern crate std; extern crate alloc; +// Required to make proc macros work in bevy itself. +extern crate self as bevy_state; + #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods pub mod app; diff --git a/crates/bevy_state/src/reflect.rs b/crates/bevy_state/src/reflect.rs index c620cd4638466..0ff09c3759d21 100644 --- a/crates/bevy_state/src/reflect.rs +++ b/crates/bevy_state/src/reflect.rs @@ -98,7 +98,6 @@ impl FromType for ReflectFreelyMu #[cfg(test)] mod tests { - use crate as bevy_state; use crate::{ app::{AppExtStates, StatesPlugin}, reflect::{ReflectFreelyMutableState, ReflectState}, diff --git a/crates/bevy_state/src/state/mod.rs b/crates/bevy_state/src/state/mod.rs index c71827cc5f06b..9267478281575 100644 --- a/crates/bevy_state/src/state/mod.rs +++ b/crates/bevy_state/src/state/mod.rs @@ -22,7 +22,6 @@ mod tests { use bevy_state_macros::{States, SubStates}; use super::*; - use crate as bevy_state; #[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)] enum SimpleState { diff --git a/crates/bevy_state/src/state/transitions.rs b/crates/bevy_state/src/state/transitions.rs index 4c4311a9d53a7..2d9b5d92d1e2f 100644 --- a/crates/bevy_state/src/state/transitions.rs +++ b/crates/bevy_state/src/state/transitions.rs @@ -145,7 +145,7 @@ pub(crate) fn internal_apply_state_transition( // Transition events are sent even for same state transitions // Although enter and exit schedules are not run by default. - event.send(StateTransitionEvent { + event.write(StateTransitionEvent { exited: Some(exited.clone()), entered: Some(entered.clone()), }); @@ -154,7 +154,7 @@ pub(crate) fn internal_apply_state_transition( // If the [`State`] resource does not exist, we create it, compute dependent states, send a transition event and register the `OnEnter` schedule. commands.insert_resource(State(entered.clone())); - event.send(StateTransitionEvent { + event.write(StateTransitionEvent { exited: None, entered: Some(entered.clone()), }); @@ -166,7 +166,7 @@ pub(crate) fn internal_apply_state_transition( if let Some(resource) = current_state { commands.remove_resource::>(); - event.send(StateTransitionEvent { + event.write(StateTransitionEvent { exited: Some(resource.get().clone()), entered: None, }); diff --git a/crates/bevy_tasks/LICENSE-APACHE b/crates/bevy_tasks/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_tasks/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_tasks/LICENSE-MIT b/crates/bevy_tasks/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_tasks/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_text/LICENSE-APACHE b/crates/bevy_text/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_text/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_text/LICENSE-MIT b/crates/bevy_text/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_text/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_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index 793cec0d2fa4a..e14157cb38f8a 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -97,7 +97,7 @@ impl FontAtlas { let atlas_layout = atlas_layouts.get_mut(&self.texture_atlas).unwrap(); let atlas_texture = textures.get_mut(&self.texture).unwrap(); - if let Some(glyph_index) = + if let Ok(glyph_index) = self.dynamic_texture_atlas_builder .add_texture(atlas_layout, texture, atlas_texture) { diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 7f71f486e4eb6..670f793c31c15 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -67,9 +67,9 @@ pub mod prelude { } use bevy_app::{prelude::*, Animation}; -use bevy_asset::AssetApp; #[cfg(feature = "default_font")] use bevy_asset::{load_internal_binary_asset, Handle}; +use bevy_asset::{AssetApp, AssetEvents}; use bevy_ecs::prelude::*; use bevy_render::{ camera::CameraUpdateSystem, view::VisibilitySystems, ExtractSchedule, RenderApp, @@ -124,7 +124,7 @@ impl Plugin for TextPlugin { .add_systems( PostUpdate, ( - remove_dropped_font_atlas_sets, + remove_dropped_font_atlas_sets.before(AssetEvents), detect_text_needs_rerender::, update_text2d_layout // Potential conflict: `Assets` diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index f564a3078a7bb..7c88010de9118 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -86,6 +86,16 @@ impl ComputedTextBlock { pub fn needs_rerender(&self) -> bool { self.needs_rerender } + /// Accesses the underlying buffer which can be used for `cosmic-text` APIs such as accessing layout information + /// or calculating a cursor position. + /// + /// Mutable access is not offered because changes would be overwritten during the automated layout calculation. + /// If you want to control the buffer contents manually or use the `cosmic-text` + /// editor, then you need to not use `TextLayout` and instead manually implement the conversion to + /// `TextLayoutInfo`. + pub fn buffer(&self) -> &CosmicBuffer { + &self.buffer + } } impl Default for ComputedTextBlock { diff --git a/crates/bevy_time/LICENSE-APACHE b/crates/bevy_time/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_time/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_time/LICENSE-MIT b/crates/bevy_time/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_time/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_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 334ff549a958c..2f37ead16a8a6 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -294,13 +294,13 @@ mod tests { .add_event::>() .add_event::>() .add_systems(Startup, move |mut ev2: EventWriter>| { - ev2.send(TestEvent { + ev2.write(TestEvent { sender: tx2.clone(), }); }) .add_systems(Update, move |mut ev1: EventWriter>| { // Keep adding events so this event type is processed every update - ev1.send(TestEvent { + ev1.write(TestEvent { sender: tx1.clone(), }); }) diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 706f38b682cdd..56db18a2f5e3c 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -12,8 +12,11 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, optional = true } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, optional = true } +bevy_log = { path = "../bevy_log", version = "0.16.0-dev", default-features = false, optional = true } bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true } +bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, optional = true } +bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, optional = true } serde = { version = "1", default-features = false, features = [ "derive", ], optional = true } @@ -30,7 +33,7 @@ approx = "0.5.1" [features] # Turning off default features leaves you with a barebones # definition of transform. -default = ["std", "bevy-support", "bevy_reflect"] +default = ["std", "bevy-support", "bevy_reflect", "async_executor"] # Functionality @@ -52,6 +55,21 @@ bevy_reflect = [ "bevy_app/bevy_reflect", ] +# Executor Backend + +## Uses `async-executor` as a task execution backend. +## This backend is incompatible with `no_std` targets. +async_executor = [ + "std", + "dep:bevy_tasks", + "dep:bevy_utils", + "bevy_tasks/async_executor", +] + +## Uses `edge-executor` as a task execution backend. +## Use this instead of `async-executor` if working on a `no_std` target. +edge_executor = ["dep:bevy_tasks", "bevy_tasks/edge_executor"] + # Platform Compatibility ## Allows access to the `std` crate. Enabling this feature will prevent compilation @@ -60,12 +78,24 @@ bevy_reflect = [ std = [ "alloc", "bevy_app?/std", + "bevy_log", "bevy_ecs?/std", "bevy_math/std", "bevy_reflect?/std", + "bevy_tasks?/std", + "bevy_utils?/std", "serde?/std", ] +## `critical-section` provides the building blocks for synchronization primitives +## on all platforms, including `no_std`. +critical-section = [ + "bevy_app?/critical-section", + "bevy_ecs?/critical-section", + "bevy_tasks?/critical-section", + "bevy_reflect?/critical-section", +] + ## Allows access to the `alloc` crate. alloc = ["serde?/alloc"] diff --git a/crates/bevy_transform/LICENSE-APACHE b/crates/bevy_transform/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_transform/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_transform/LICENSE-MIT b/crates/bevy_transform/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_transform/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_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 18ecfb16b67d0..777ca81a9dbd5 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -52,11 +52,11 @@ fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformErr use ComputeGlobalTransformError::*; match err { QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity), - QueryEntityError::NoSuchEntity(entity, _) => { + QueryEntityError::EntityDoesNotExist(error) => { if ancestor { - MalformedHierarchy(entity) + MalformedHierarchy(error.entity) } else { - NoSuchEntity(entity) + NoSuchEntity(error.entity) } } QueryEntityError::AliasedMutability(_) => unreachable!(), diff --git a/crates/bevy_transform/src/plugins.rs b/crates/bevy_transform/src/plugins.rs index 1add9713a0be9..83d69c0f025ce 100644 --- a/crates/bevy_transform/src/plugins.rs +++ b/crates/bevy_transform/src/plugins.rs @@ -1,4 +1,6 @@ -use crate::systems::{propagate_transforms, sync_simple_transforms}; +use crate::systems::{ + compute_transform_leaves, propagate_parent_transforms, sync_simple_transforms, +}; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet}; @@ -32,14 +34,12 @@ impl Plugin for TransformPlugin { .add_systems( PostStartup, ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - // FIXME: https://github.com/bevyengine/bevy/issues/4381 - // These systems cannot access the same entities, - // due to subtle query filtering that is not yet correctly computed in the ambiguity detector - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), + propagate_parent_transforms, + (compute_transform_leaves, sync_simple_transforms) + .ambiguous_with(TransformSystem::TransformPropagate), + ) + .chain() + .in_set(PropagateTransformsSet), ) .configure_sets( PostUpdate, @@ -48,11 +48,12 @@ impl Plugin for TransformPlugin { .add_systems( PostUpdate, ( - sync_simple_transforms - .in_set(TransformSystem::TransformPropagate) - .ambiguous_with(PropagateTransformsSet), - propagate_transforms.in_set(PropagateTransformsSet), - ), + propagate_parent_transforms, + (compute_transform_leaves, sync_simple_transforms) // TODO: Adjust the internal parallel queries to make these parallel systems more efficiently share and fill CPU time. + .ambiguous_with(TransformSystem::TransformPropagate), + ) + .chain() + .in_set(PropagateTransformsSet), ); } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 41291ab55726f..d9c6f0932fdae 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -1,10 +1,15 @@ use crate::components::{GlobalTransform, Transform}; -use alloc::vec::Vec; use bevy_ecs::prelude::*; +#[cfg(feature = "std")] +pub use parallel::propagate_parent_transforms; +#[cfg(not(feature = "std"))] +pub use serial::propagate_parent_transforms; + /// Update [`GlobalTransform`] component of entities that aren't in the hierarchy /// -/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`]. +/// Third party plugins should ensure that this is used in concert with +/// [`propagate_parent_transforms`] and [`compute_transform_leaves`]. pub fn sync_simple_transforms( mut query: ParamSet<( Query< @@ -36,27 +41,74 @@ pub fn sync_simple_transforms( } } -/// Update [`GlobalTransform`] component of entities based on entity hierarchy and -/// [`Transform`] component. +/// Compute leaf [`GlobalTransform`]s in parallel. /// -/// Third party plugins should ensure that this is used in concert with [`sync_simple_transforms`]. -pub fn propagate_transforms( - mut root_query: Query< - (Entity, &Children, Ref, &mut GlobalTransform), - Without, - >, - mut orphaned: RemovedComponents, - transform_query: Query< - (Ref, &mut GlobalTransform, Option<&Children>), - With, - >, - parent_query: Query<(Entity, Ref), With>, - mut orphaned_entities: Local>, +/// This is run after [`propagate_parent_transforms`], to ensure the parents' [`GlobalTransform`]s +/// have been computed. This makes computing leaf nodes at different levels of the hierarchy much +/// more cache friendly, because data can be iterated over densely from the same archetype. +pub fn compute_transform_leaves( + parents: Query, With>, + mut leaves: Query<(Ref, &mut GlobalTransform, &ChildOf), Without>, ) { - orphaned_entities.clear(); - orphaned_entities.extend(orphaned.read()); - orphaned_entities.sort_unstable(); - root_query.par_iter_mut().for_each( + leaves + .par_iter_mut() + .for_each(|(transform, mut global_transform, parent)| { + let Ok(parent_transform) = parents.get(parent.get()) else { + return; + }; + if parent_transform.is_changed() + || transform.is_changed() + || global_transform.is_added() + { + *global_transform = parent_transform.mul_transform(*transform); + } + }); +} + +// TODO: This serial implementation isn't actually serial, it parallelizes across the roots. +// Additionally, this couples "no_std" with "single_threaded" when these two features should be +// independent. +// +// What we want to do in a future refactor is take the current "single threaded" implementation, and +// actually make it single threaded. This will remove any overhead associated with working on a task +// pool when you only have a single thread, and will have the benefit of removing the need for any +// unsafe. We would then make the multithreaded implementation work across std and no_std, but this +// is blocked a no_std compatible Channel, which is why this TODO is not yet implemented. +// +// This complexity might also not be needed. If the multithreaded implementation on a single thread +// is as fast as the single threaded implementation, we could simply remove the entire serial +// module, and make the multithreaded module no_std compatible. +// +/// Serial hierarchy traversal. Useful in `no_std` or single threaded contexts. +#[cfg(not(feature = "std"))] +mod serial { + use crate::prelude::*; + use alloc::vec::Vec; + use bevy_ecs::prelude::*; + + /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`] + /// component. + /// + /// Third party plugins should ensure that this is used in concert with + /// [`sync_simple_transforms`](super::sync_simple_transforms) and + /// [`compute_transform_leaves`](super::compute_transform_leaves). + pub fn propagate_parent_transforms( + mut root_query: Query< + (Entity, &Children, Ref, &mut GlobalTransform), + Without, + >, + mut orphaned: RemovedComponents, + transform_query: Query< + (Ref, &mut GlobalTransform, Option<&Children>), + (With, With), + >, + parent_query: Query<(Entity, Ref), With>, + mut orphaned_entities: Local>, + ) { + orphaned_entities.clear(); + orphaned_entities.extend(orphaned.read()); + orphaned_entities.sort_unstable(); + root_query.par_iter_mut().for_each( |(entity, children, transform, mut global_transform)| { let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok(); if changed { @@ -70,12 +122,16 @@ pub fn propagate_transforms( ); // SAFETY: // - `child` must have consistent parentage, or the above assertion would panic. - // Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent. - // - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before - // continuing to propagate if it encounters an entity with inconsistent parentage. - // - Since each root entity is unique and the hierarchy is consistent and forest-like, - // other root entities' `propagate_recursive` calls will not conflict with this one. - // - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere. + // Since `child` is parented to a root entity, the entire hierarchy leading to it + // is consistent. + // - We may operate as if all descendants are consistent, since + // `propagate_recursive` will panic before continuing to propagate if it + // encounters an entity with inconsistent parentage. + // - Since each root entity is unique and the hierarchy is consistent and + // forest-like, other root entities' `propagate_recursive` calls will not conflict + // with this one. + // - Since this is the only place where `transform_query` gets used, there will be + // no conflicting fetches elsewhere. #[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")] unsafe { propagate_recursive( @@ -89,41 +145,41 @@ pub fn propagate_transforms( } }, ); -} + } -/// Recursively propagates the transforms for `entity` and all of its descendants. -/// -/// # Panics -/// -/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating -/// the transforms of any malformed entities and their descendants. -/// -/// # Safety -/// -/// - While this function is running, `transform_query` must not have any fetches for `entity`, -/// nor any of its descendants. -/// - The caller must ensure that the hierarchy leading to `entity` -/// is well-formed and must remain as a tree or a forest. Each entity must have at most one parent. -#[expect( - unsafe_code, - reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met." -)] -unsafe fn propagate_recursive( - parent: &GlobalTransform, - transform_query: &Query< - (Ref, &mut GlobalTransform, Option<&Children>), - With, - >, - parent_query: &Query<(Entity, Ref), With>, - entity: Entity, - mut changed: bool, -) { - let (global_matrix, children) = { - let Ok((transform, mut global_transform, children)) = + /// Recursively propagates the transforms for `entity` and all of its descendants. + /// + /// # Panics + /// + /// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before + /// propagating the transforms of any malformed entities and their descendants. + /// + /// # Safety + /// + /// - While this function is running, `transform_query` must not have any fetches for `entity`, + /// nor any of its descendants. + /// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must + /// remain as a tree or a forest. Each entity must have at most one parent. + #[expect( + unsafe_code, + reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met." + )] + unsafe fn propagate_recursive( + parent: &GlobalTransform, + transform_query: &Query< + (Ref, &mut GlobalTransform, Option<&Children>), + (With, With), + >, + parent_query: &Query<(Entity, Ref), With>, + entity: Entity, + mut changed: bool, + ) { + let (global_matrix, children) = { + let Ok((transform, mut global_transform, children)) = // SAFETY: This call cannot create aliased mutable references. // - The top level iteration parallelizes on the roots of the hierarchy. - // - The caller ensures that each child has one and only one unique parent throughout the entire - // hierarchy. + // - The caller ensures that each child has one and only one unique parent throughout + // the entire hierarchy. // // For example, consider the following malformed hierarchy: // @@ -133,8 +189,9 @@ unsafe fn propagate_recursive( // \ / // D // - // D has two parents, B and C. If the propagation passes through C, but the ChildOf component on D points to B, - // the above check will panic as the origin parent does match the recorded parent. + // D has two parents, B and C. If the propagation passes through C, but the ChildOf + // component on D points to B, the above check will panic as the origin parent does + // match the recorded parent. // // Also consider the following case, where A and B are roots: // @@ -144,45 +201,368 @@ unsafe fn propagate_recursive( // \ / // E // - // Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting - // to mutably access E. + // Even if these A and B start two separate tasks running in parallel, one of them will + // panic before attempting to mutably access E. (unsafe { transform_query.get_unchecked(entity) }) else { return; }; - changed |= transform.is_changed() || global_transform.is_added(); - if changed { - *global_transform = parent.mul_transform(*transform); - } - (global_transform, children) - }; + changed |= transform.is_changed() || global_transform.is_added(); + if changed { + *global_transform = parent.mul_transform(*transform); + } + (global_transform, children) + }; - let Some(children) = children else { return }; - for (child, actual_parent) in parent_query.iter_many(children) { - assert_eq!( + let Some(children) = children else { return }; + for (child, actual_parent) in parent_query.iter_many(children) { + assert_eq!( actual_parent.get(), entity, "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" ); - // SAFETY: The caller guarantees that `transform_query` will not be fetched - // for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child. - // - // The above assertion ensures that each child has one and only one unique parent throughout the - // entire hierarchy. - unsafe { - propagate_recursive( - global_matrix.as_ref(), - transform_query, - parent_query, - child, - changed || actual_parent.is_changed(), + // SAFETY: The caller guarantees that `transform_query` will not be fetched for any + // descendants of `entity`, so it is safe to call `propagate_recursive` for each child. + // + // The above assertion ensures that each child has one and only one unique parent + // throughout the entire hierarchy. + unsafe { + propagate_recursive( + global_matrix.as_ref(), + transform_query, + parent_query, + child, + changed || actual_parent.is_changed(), + ); + } + } + } +} + +// TODO: Relies on `std` until a `no_std` `mpsc` channel is available. +// +/// Parallel hierarchy traversal with a batched work sharing scheduler. Often 2-5 times faster than +/// the serial version. +#[cfg(feature = "std")] +mod parallel { + use crate::prelude::*; + use bevy_ecs::{entity::UniqueEntityIter, prelude::*, system::lifetimeless::Read}; + use bevy_tasks::{ComputeTaskPool, TaskPool}; + use bevy_utils::Parallel; + use core::sync::atomic::{AtomicI32, Ordering}; + // TODO: this implementation could be used in no_std if there are equivalents of these. + use std::{ + sync::{ + mpsc::{Receiver, Sender}, + Arc, Mutex, + }, + vec::Vec, + }; + + /// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`] + /// component. + /// + /// Third party plugins should ensure that this is used in concert with + /// [`sync_simple_transforms`](super::sync_simple_transforms) and + /// [`compute_transform_leaves`](super::compute_transform_leaves). + pub fn propagate_parent_transforms( + mut queue: Local, + mut orphaned: RemovedComponents, + mut orphans: Local>, + mut roots: Query< + (Entity, Ref, &mut GlobalTransform, &Children), + Without, + >, + nodes: NodeQuery, + ) { + // Orphans + orphans.clear(); + orphans.extend(orphaned.read()); + orphans.sort_unstable(); + + // Process roots in parallel, seeding the work queue + roots.par_iter_mut().for_each_init( + || queue.local_queue.borrow_local_mut(), + |outbox, (parent, transform, mut parent_transform, children)| { + if transform.is_changed() + || parent_transform.is_added() + || orphans.binary_search(&parent).is_ok() + { + *parent_transform = GlobalTransform::from(*transform); + } + + // SAFETY: the parent entities passed into this function are taken from iterating + // over the root entity query. Queries iterate over disjoint entities, preventing + // mutable aliasing, and making this call safe. + #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")] + unsafe { + propagate_descendants_unchecked( + parent, + parent_transform, + children, + &nodes, + outbox, + &queue, + // Need to revisit this single-max-depth by profiling more representative + // scenes. It's possible that it is actually beneficial to go deep into the + // hierarchy to build up a good task queue before starting the workers. + // However, we avoid this for now to prevent cases where only a single + // thread is going deep into the hierarchy while the others sit idle, which + // is the problem that the tasks sharing workers already solve. + 1, + ); + } + }, + ); + // Send all tasks in thread local outboxes *after* roots are processed to reduce the total + // number of channel sends by avoiding sending partial batches. + queue.send_batches(); + + // Spawn workers on the task pool to recursively propagate the hierarchy in parallel. + let task_pool = ComputeTaskPool::get_or_init(TaskPool::default); + task_pool.scope(|s| { + (1..task_pool.thread_num()) // First worker is run locally instead of the task pool. + .for_each(|_| s.spawn(async { propagation_worker(&queue, &nodes) })); + propagation_worker(&queue, &nodes); + }); + } + + /// A parallel worker that will consume processed parent entities from the queue, and push + /// children to the queue once it has propagated their [`GlobalTransform`]. + #[inline] + fn propagation_worker(queue: &WorkQueue, nodes: &NodeQuery) { + #[cfg(feature = "std")] + let _span = bevy_log::info_span!("transform propagation worker").entered(); + + let mut outbox = queue.local_queue.borrow_local_mut(); + loop { + // Try to acquire a lock on the work queue in a tight loop. Profiling shows this is much + // more efficient than relying on `.lock()`, which causes gaps to form between tasks. + let Ok(rx) = queue.receiver.try_lock() else { + core::hint::spin_loop(); // No apparent impact on profiles, but best practice. + continue; + }; + // If the queue is empty and no other threads are busy processing work, we can conclude + // there is no more work to do, and end the task by exiting the loop. + let Some(mut tasks) = rx.try_iter().next() else { + if queue.busy_threads.load(Ordering::Relaxed) == 0 { + break; // All work is complete, kill the worker + } + continue; // No work to do now, but another thread is busy creating more work. + }; + if tasks.is_empty() { + continue; // This shouldn't happen, but if it does, we might as well stop early. + } + + // If the task queue is extremely short, it's worthwhile to gather a few more tasks to + // reduce the amount of thread synchronization needed once this very short task is + // complete. + while tasks.len() < WorkQueue::CHUNK_SIZE / 2 { + let Some(mut extra_task) = rx.try_iter().next() else { + break; + }; + tasks.append(&mut extra_task); + } + + // At this point, we know there is work to do, so we increment the busy thread counter, + // and drop the mutex guard *after* we have incremented the counter. This ensures that + // if another thread is able to acquire a lock, the busy thread counter will already be + // incremented. + queue.busy_threads.fetch_add(1, Ordering::Relaxed); + drop(rx); // Important: drop after atomic and before work starts. + + for parent in tasks.drain(..) { + // SAFETY: each task pushed to the worker queue represents an unprocessed subtree of + // the hierarchy, guaranteeing unique access. + #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")] + unsafe { + let (_, (_, p_global_transform), (p_children, _)) = + nodes.get_unchecked(parent).unwrap(); + propagate_descendants_unchecked( + parent, + p_global_transform, + p_children, + nodes, + &mut outbox, + queue, + // Only affects performance. Trees deeper than this will still be fully + // propagated, but the work will be broken into multiple tasks. This number + // was chosen to be larger than any reasonable tree depth, while not being + // so large the function could hang on a deep hierarchy. + 10_000, + ); + } + } + WorkQueue::send_batches_with(&queue.sender, &mut outbox); + queue.busy_threads.fetch_add(-1, Ordering::Relaxed); + } + } + + /// Propagate transforms from `parent` to its non-leaf `children`, pushing updated child + /// entities to the `outbox`. Propagation does not visit leaf nodes; instead, they are computed + /// in [`compute_transform_leaves`](super::compute_transform_leaves), which can optimize much + /// more efficiently. + /// + /// This function will continue propagating transforms to descendants in a depth-first + /// traversal, while simultaneously pushing unvisited branches to the outbox, for other threads + /// to take when idle. + /// + /// # Safety + /// + /// Callers must ensure that concurrent calls to this function are given unique `parent` + /// entities. Calling this function concurrently with the same `parent` is unsound. This + /// function will validate that the entity hierarchy does not contain cycles to prevent mutable + /// aliasing during propagation, but it is unable to verify that it isn't being used to mutably + /// alias the same entity. + /// + /// ## Panics + /// + /// Panics if the parent of a child node is not the same as the supplied `parent`. This + /// assertion ensures that the hierarchy is acyclic, which in turn ensures that if the caller is + /// following the supplied safety rules, multi-threaded propagation is sound. + #[inline] + #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")] + unsafe fn propagate_descendants_unchecked( + parent: Entity, + p_global_transform: Mut, + p_children: &Children, + nodes: &NodeQuery, + outbox: &mut Vec, + queue: &WorkQueue, + max_depth: usize, + ) { + // Create mutable copies of the input variables, used for iterative depth-first traversal. + let (mut parent, mut p_global_transform, mut p_children) = + (parent, p_global_transform, p_children); + + // See the optimization note at the end to understand why this loop is here. + for depth in 1..=max_depth { + // Safety: traversing the entity tree from the roots, we assert that the childof and + // children pointers match in both directions (see assert below) to ensure the hierarchy + // does not have any cycles. Because the hierarchy does not have cycles, we know we are + // visiting disjoint entities in parallel, which is safe. + #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")] + let children_iter = unsafe { + // Performance note: iter_many tests every child to see if it meets the query. For + // leaf nodes, this unfortunately means we have the pay the price of checking every + // child, even if it is a leaf node and is skipped. + // + // To ensure this is still the fastest design, I tried removing the second pass + // (`compute_transform_leaves`) and instead simply doing that here. However, that + // proved to be much slower than two pass for a few reasons: + // - it's less cache friendly and is outright slower than the tight loop in the + // second pass + // - it prevents parallelism, as all children must be iterated in series + // + // The only way I can see to make this faster when there are many leaf nodes is to + // speed up archetype checking to make the iterator skip leaf entities more quickly, + // or encoding the hierarchy level as a component. That, or use some kind of change + // detection to mark dirty subtrees when the transform is mutated. + nodes.iter_many_unique_unsafe(UniqueEntityIter::from_iterator_unchecked( + p_children.iter(), + )) + }; + + let mut last_child = None; + let new_children = children_iter.map( + |(child, (transform, mut global_transform), (children, child_of))| { + assert_eq!(child_of.get(), parent); + if p_global_transform.is_changed() + || transform.is_changed() + || global_transform.is_added() + { + *global_transform = p_global_transform.mul_transform(*transform); + } + last_child = Some((child, global_transform, children)); + child + }, ); + outbox.extend(new_children); + + if depth >= max_depth || last_child.is_none() { + break; // Don't remove anything from the outbox or send any chunks, just exit. + } + + // Optimization: tasks should consume work locally as long as they can to avoid + // thread synchronization for as long as possible. + if let Some(last_child) = last_child { + // Overwrite parent data with children, and loop to iterate through descendants. + (parent, p_global_transform, p_children) = last_child; + outbox.pop(); + + // Send chunks during traversal. This allows sharing tasks with other threads before + // fully completing the traversal. + if outbox.len() >= WorkQueue::CHUNK_SIZE { + WorkQueue::send_batches_with(&queue.sender, outbox); + } + } + } + } + + /// Alias for a large, repeatedly used query. Queries for transform entities that have both a + /// parent and children, thus they are neither roots nor leaves. + type NodeQuery<'w, 's> = Query< + 'w, + 's, + ( + Entity, + (Ref<'static, Transform>, Mut<'static, GlobalTransform>), + (Read, Read), + ), + >; + + /// A queue shared between threads for transform propagation. + pub struct WorkQueue { + /// A semaphore that tracks how many threads are busy doing work. Used to determine when + /// there is no more work to do. + busy_threads: AtomicI32, + sender: Sender>, + receiver: Arc>>>, + local_queue: Parallel>, + } + impl Default for WorkQueue { + fn default() -> Self { + let (tx, rx) = std::sync::mpsc::channel(); + Self { + busy_threads: AtomicI32::default(), + sender: tx, + receiver: Arc::new(Mutex::new(rx)), + local_queue: Default::default(), + } + } + } + impl WorkQueue { + const CHUNK_SIZE: usize = 512; + + #[inline] + fn send_batches_with(sender: &Sender>, outbox: &mut Vec) { + for chunk in outbox + .chunks(WorkQueue::CHUNK_SIZE) + .filter(|c| !c.is_empty()) + { + sender.send(chunk.to_vec()).ok(); + } + outbox.clear(); + } + + #[inline] + fn send_batches(&mut self) { + let Self { + sender, + local_queue, + .. + } = self; + // Iterate over the locals to send batched tasks, avoiding the need to drain the locals + // into a larger allocation. + local_queue + .iter_mut() + .for_each(|outbox| Self::send_batches_with(sender, outbox)); } } } #[cfg(test)] mod test { - use alloc::vec; + use alloc::{vec, vec::Vec}; use bevy_app::prelude::*; use bevy_ecs::{prelude::*, world::CommandQueue}; use bevy_math::{vec3, Vec3}; @@ -199,7 +579,14 @@ mod test { let offset_transform = |offset| Transform::from_xyz(offset, offset, offset); let mut schedule = Schedule::default(); - schedule.add_systems((sync_simple_transforms, propagate_transforms)); + schedule.add_systems( + ( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + ) + .chain(), + ); let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); @@ -250,7 +637,14 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::default(); - schedule.add_systems((sync_simple_transforms, propagate_transforms)); + schedule.add_systems( + ( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + ) + .chain(), + ); // Root entity world.spawn(Transform::from_xyz(1.0, 0.0, 0.0)); @@ -280,7 +674,14 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::default(); - schedule.add_systems((sync_simple_transforms, propagate_transforms)); + schedule.add_systems( + ( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + ) + .chain(), + ); // Root entity let mut queue = CommandQueue::default(); @@ -312,7 +713,14 @@ mod test { let mut world = World::default(); let mut schedule = Schedule::default(); - schedule.add_systems((sync_simple_transforms, propagate_transforms)); + schedule.add_systems( + ( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + ) + .chain(), + ); // Add parent entities let mut children = Vec::new(); @@ -334,7 +742,6 @@ mod test { .get::(parent) .unwrap() .iter() - .cloned() .collect::>(), children, ); @@ -353,7 +760,6 @@ mod test { .get::(parent) .unwrap() .iter() - .cloned() .collect::>(), vec![children[1]] ); @@ -363,7 +769,6 @@ mod test { .get::(children[1]) .unwrap() .iter() - .cloned() .collect::>(), vec![children[0]] ); @@ -377,7 +782,6 @@ mod test { .get::(parent) .unwrap() .iter() - .cloned() .collect::>(), vec![children[1]] ); @@ -388,7 +792,15 @@ mod test { let mut app = App::new(); ComputeTaskPool::get_or_init(TaskPool::default); - app.add_systems(Update, (sync_simple_transforms, propagate_transforms)); + app.add_systems( + Update, + ( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + ) + .chain(), + ); let translation = vec3(1.0, 0.0, 0.0); @@ -416,7 +828,8 @@ mod test { &**app.world().get::(child).unwrap(), &[grandchild] ); - // Note that at this point, the `GlobalTransform`s will not have updated yet, due to `Commands` delay + // Note that at this point, the `GlobalTransform`s will not have updated yet, due to + // `Commands` delay app.update(); let mut state = app.world_mut().query::<&GlobalTransform>(); @@ -429,12 +842,20 @@ mod test { #[should_panic] fn panic_when_hierarchy_cycle() { ComputeTaskPool::get_or_init(TaskPool::default); - // We cannot directly edit ChildOf and Children, so we use a temp world to break - // the hierarchy's invariants. + // We cannot directly edit ChildOf and Children, so we use a temp world to break the + // hierarchy's invariants. let mut temp = World::new(); let mut app = App::new(); - app.add_systems(Update, (propagate_transforms, sync_simple_transforms)); + app.add_systems( + Update, + ( + propagate_parent_transforms, + sync_simple_transforms, + compute_transform_leaves, + ) + .chain(), + ); fn setup_world(world: &mut World) -> (Entity, Entity) { let mut grandchild = Entity::from_raw(0); @@ -461,7 +882,8 @@ mod test { unsafe_code, reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen" )] - // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that cannot happen + // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that + // cannot happen unsafe { &mut *app .world_mut() @@ -469,7 +891,8 @@ mod test { .get_mut_assume_mutable::() .unwrap() }, - // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that cannot happen + // SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that + // cannot happen #[expect( unsafe_code, reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen" @@ -492,7 +915,11 @@ mod test { // Create transform propagation schedule let mut schedule = Schedule::default(); - schedule.add_systems((sync_simple_transforms, propagate_transforms)); + schedule.add_systems(( + sync_simple_transforms, + propagate_parent_transforms, + compute_transform_leaves, + )); // Spawn a `Transform` entity with a local translation of `Vec3::ONE` let mut spawn_transform_bundle = diff --git a/crates/bevy_ui/LICENSE-APACHE b/crates/bevy_ui/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_ui/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_ui/LICENSE-MIT b/crates/bevy_ui/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_ui/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_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index f00209efac51e..8e663964f7eb9 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -1,44 +1,28 @@ //! This module contains [`GhostNode`] and utilities to flatten the UI hierarchy, traversing past ghost nodes. +#[cfg(feature = "ghost_nodes")] +use crate::ui_node::ComputedNodeTarget; use crate::Node; use bevy_ecs::{prelude::*, system::SystemParam}; +#[cfg(feature = "ghost_nodes")] use bevy_reflect::prelude::*; +#[cfg(feature = "ghost_nodes")] use bevy_render::view::Visibility; +#[cfg(feature = "ghost_nodes")] use bevy_transform::prelude::Transform; -use core::marker::PhantomData; - #[cfg(feature = "ghost_nodes")] use smallvec::SmallVec; - /// Marker component for entities that should be ignored within UI hierarchies. /// /// The UI systems will traverse past these and treat their first non-ghost descendants as direct children of their first non-ghost ancestor. /// /// Any components necessary for transform and visibility propagation will be added automatically. -/// -/// Instances of this type cannot be constructed unless the `ghost_nodes` feature is enabled. +#[cfg(feature = "ghost_nodes")] #[derive(Component, Debug, Copy, Clone, Reflect)] #[cfg_attr(feature = "ghost_nodes", derive(Default))] #[reflect(Component, Debug)] -#[require(Visibility, Transform)] -pub struct GhostNode { - // This is a workaround to ensure that GhostNode is only constructable when the appropriate feature flag is enabled - #[reflect(ignore)] - unconstructable: PhantomData<()>, // Spooky! -} - -#[cfg(feature = "ghost_nodes")] -impl GhostNode { - /// Creates a new ghost node. - /// - /// This method is only available when the `ghost_node` feature is enabled, - /// and will eventually be deprecated then removed in favor of simply using `GhostNode` as no meaningful data is stored. - pub const fn new() -> Self { - GhostNode { - unconstructable: PhantomData, - } - } -} +#[require(Visibility, Transform, ComputedNodeTarget)] +pub struct GhostNode; #[cfg(feature = "ghost_nodes")] /// System param that allows iteration of all UI root nodes. @@ -202,7 +186,7 @@ impl<'w, 's> Iterator for UiChildrenIter<'w, 's> { return Some(entity); } if let Some(children) = children { - self.stack.extend(children.iter().rev().copied()); + self.stack.extend(children.iter().rev()); } } } @@ -232,20 +216,18 @@ mod tests { .with_children(|parent| { parent.spawn((A(2), Node::default())); parent - .spawn((A(3), GhostNode::new())) + .spawn((A(3), GhostNode)) .with_child((A(4), Node::default())); }); // Ghost root - world - .spawn((A(5), GhostNode::new())) - .with_children(|parent| { - parent.spawn((A(6), Node::default())); - parent - .spawn((A(7), GhostNode::new())) - .with_child((A(8), Node::default())) - .with_child(A(9)); - }); + world.spawn((A(5), GhostNode)).with_children(|parent| { + parent.spawn((A(6), Node::default())); + parent + .spawn((A(7), GhostNode)) + .with_child((A(8), Node::default())) + .with_child(A(9)); + }); let mut system_state = SystemState::<(UiRootNodes, Query<&A>)>::new(world); let (ui_root_nodes, a_query) = system_state.get(world); @@ -260,15 +242,15 @@ mod tests { let world = &mut World::new(); let n1 = world.spawn((A(1), Node::default())).id(); - let n2 = world.spawn((A(2), GhostNode::new())).id(); - let n3 = world.spawn((A(3), GhostNode::new())).id(); + let n2 = world.spawn((A(2), GhostNode)).id(); + let n3 = world.spawn((A(3), GhostNode)).id(); let n4 = world.spawn((A(4), Node::default())).id(); let n5 = world.spawn((A(5), Node::default())).id(); - let n6 = world.spawn((A(6), GhostNode::new())).id(); - let n7 = world.spawn((A(7), GhostNode::new())).id(); + let n6 = world.spawn((A(6), GhostNode)).id(); + let n7 = world.spawn((A(7), GhostNode)).id(); let n8 = world.spawn((A(8), Node::default())).id(); - let n9 = world.spawn((A(9), GhostNode::new())).id(); + let n9 = world.spawn((A(9), GhostNode)).id(); let n10 = world.spawn((A(10), Node::default())).id(); let no_ui = world.spawn_empty().id(); diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 126f0d48ff35f..4093b81d55347 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,6 +1,4 @@ -use crate::{ - CalculatedClip, ComputedNode, DefaultUiCamera, ResolvedBorderRadius, UiStack, UiTargetCamera, -}; +use crate::{CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius, UiStack}; use bevy_ecs::{ change_detection::DetectChangesMut, entity::{Entity, EntityBorrow}, @@ -141,7 +139,7 @@ pub struct NodeQuery { focus_policy: Option<&'static FocusPolicy>, calculated_clip: Option<&'static CalculatedClip>, inherited_visibility: Option<&'static InheritedVisibility>, - target_camera: Option<&'static UiTargetCamera>, + target_camera: &'static ComputedNodeTarget, } /// The system that sets Interaction for all UI elements based on the mouse cursor activity @@ -150,7 +148,6 @@ pub struct NodeQuery { pub fn ui_focus_system( mut state: Local, camera_query: Query<(Entity, &Camera)>, - default_ui_camera: DefaultUiCamera, primary_window: Query>, windows: Query<&Window>, mouse_button_input: Res>, @@ -212,8 +209,6 @@ pub fn ui_focus_system( }) .collect(); - let default_camera_entity = default_ui_camera.get(); - // prepare an iterator that contains all the nodes that have the cursor in their rect, // from the top node to the bottom one. this will also reset the interaction to `None` // for all nodes encountered that are no longer hovered. @@ -237,10 +232,7 @@ pub fn ui_focus_system( } return None; } - let camera_entity = node - .target_camera - .map(UiTargetCamera::entity) - .or(default_camera_entity)?; + let camera_entity = node.target_camera.camera()?; let node_rect = Rect::from_center_size( node.global_transform.translation().truncate(), diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index e83f2a3b23e99..2a8b371320482 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -14,19 +14,18 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { .iter() .map(|(entity, node)| (node.id, *entity)) .collect(); - for (&entity, roots) in &ui_surface.camera_roots { + for (&entity, &viewport_node) in &ui_surface.root_entity_to_viewport_node { let mut out = String::new(); - for root in roots { - print_node( - ui_surface, - &taffy_to_entity, - entity, - root.implicit_viewport_node, - false, - String::new(), - &mut out, - ); - } + print_node( + ui_surface, + &taffy_to_entity, + entity, + viewport_node, + false, + String::new(), + &mut out, + ); + tracing::info!("Layout tree for camera entity: {entity}\n{out}"); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 6f7875e62a7e0..16a663b07bd05 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1,19 +1,20 @@ use crate::{ experimental::{UiChildren, UiRootNodes}, - BorderRadius, ComputedNode, ContentSize, DefaultUiCamera, Display, LayoutConfig, Node, Outline, - OverflowAxis, ScrollPosition, UiScale, UiTargetCamera, Val, + BorderRadius, ComputedNode, ComputedNodeTarget, ContentSize, Display, LayoutConfig, Node, + Outline, OverflowAxis, ScrollPosition, Val, }; use bevy_ecs::{ - entity::{hash_map::EntityHashMap, hash_set::EntityHashSet}, - prelude::*, - system::SystemParam, + change_detection::{DetectChanges, DetectChangesMut}, + entity::Entity, + hierarchy::{ChildOf, Children}, + query::With, + removal_detection::RemovedComponents, + system::{Commands, Query, ResMut}, + world::Ref, }; -use bevy_math::{UVec2, Vec2}; -use bevy_render::camera::{Camera, NormalizedRenderTarget}; +use bevy_math::Vec2; use bevy_sprite::BorderRect; use bevy_transform::components::Transform; -use bevy_utils::once; -use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use thiserror::Error; use tracing::warn; use ui_surface::UiSurface; @@ -67,50 +68,19 @@ pub enum LayoutError { TaffyError(taffy::TaffyError), } -#[doc(hidden)] -#[derive(SystemParam)] -pub struct UiLayoutSystemRemovedComponentParam<'w, 's> { - removed_cameras: RemovedComponents<'w, 's, Camera>, - removed_children: RemovedComponents<'w, 's, Children>, - removed_content_sizes: RemovedComponents<'w, 's, ContentSize>, - removed_nodes: RemovedComponents<'w, 's, Node>, -} - -#[doc(hidden)] -#[derive(Default)] -pub struct UiLayoutSystemBuffers { - interned_root_nodes: Vec>, - resized_windows: EntityHashSet, - camera_layout_info: EntityHashMap, -} - -struct CameraLayoutInfo { - size: UVec2, - resized: bool, - scale_factor: f32, - root_nodes: Vec, -} - /// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. pub fn ui_layout_system( mut commands: Commands, - mut buffers: Local, - primary_window: Query<(Entity, &Window), With>, - camera_data: (Query<(Entity, &Camera)>, DefaultUiCamera), - ui_scale: Res, - mut scale_factor_events: EventReader, - mut resize_events: EventReader, mut ui_surface: ResMut, - root_nodes: UiRootNodes, + ui_root_node_query: UiRootNodes, mut node_query: Query<( Entity, Ref, Option<&mut ContentSize>, - Option<&UiTargetCamera>, + Ref, )>, computed_node_query: Query<(Entity, Option>), With>, ui_children: UiChildren, - mut removed_components: UiLayoutSystemRemovedComponentParam, mut node_transform_query: Query<( &mut ComputedNode, &mut Transform, @@ -120,127 +90,38 @@ pub fn ui_layout_system( Option<&Outline>, Option<&ScrollPosition>, )>, - mut buffer_query: Query<&mut ComputedTextBlock>, mut font_system: ResMut, + mut removed_children: RemovedComponents, + mut removed_content_sizes: RemovedComponents, + mut removed_nodes: RemovedComponents, ) { - let UiLayoutSystemBuffers { - interned_root_nodes, - resized_windows, - camera_layout_info, - } = &mut *buffers; - - let (cameras, default_ui_camera) = camera_data; - - let default_camera = default_ui_camera.get(); - let camera_with_default = |target_camera: Option<&UiTargetCamera>| { - target_camera.map(UiTargetCamera::entity).or(default_camera) - }; - - resized_windows.clear(); - resized_windows.extend(resize_events.read().map(|event| event.window)); - let mut calculate_camera_layout_info = |camera: &Camera| { - let size = camera.physical_viewport_size().unwrap_or(UVec2::ZERO); - let scale_factor = camera.target_scaling_factor().unwrap_or(1.0); - let camera_target = camera - .target - .normalize(primary_window.get_single().map(|(e, _)| e).ok()); - let resized = matches!(camera_target, - Some(NormalizedRenderTarget::Window(window_ref)) if resized_windows.contains(&window_ref.entity()) - ); - CameraLayoutInfo { - size, - resized, - scale_factor: scale_factor * ui_scale.0, - root_nodes: interned_root_nodes.pop().unwrap_or_default(), - } - }; - - // Precalculate the layout info for each camera, so we have fast access to it for each node - camera_layout_info.clear(); - - node_query - .iter_many(root_nodes.iter()) - .for_each(|(entity, _, _, target_camera)| { - match camera_with_default(target_camera) { - Some(camera_entity) => { - let Ok((_, camera)) = cameras.get(camera_entity) else { - once!(warn!( - "UiTargetCamera (of root UI node {entity}) is pointing to a camera {} which doesn't exist", - camera_entity - )); - return; - }; - let layout_info = camera_layout_info - .entry(camera_entity) - .or_insert_with(|| calculate_camera_layout_info(camera)); - layout_info.root_nodes.push(entity); - } - None => { - if cameras.is_empty() { - once!(warn!("No camera found to render UI to. To fix this, add at least one camera to the scene.")); - } else { - once!(warn!( - "Multiple cameras found, causing UI target ambiguity. \ - To fix this, add an explicit `UiTargetCamera` component to the root UI node {}", - entity - )); - } - } - } - - } - ); - // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. - for entity in removed_components.removed_content_sizes.read() { + for entity in removed_content_sizes.read() { ui_surface.try_remove_node_context(entity); } // Sync Node and ContentSize to Taffy for all nodes node_query .iter_mut() - .for_each(|(entity, node, content_size, target_camera)| { - if let Some(camera) = - camera_with_default(target_camera).and_then(|c| camera_layout_info.get(&c)) + .for_each(|(entity, node, content_size, computed_target)| { + if computed_target.is_changed() + || node.is_changed() + || content_size + .as_ref() + .is_some_and(|c| c.is_changed() || c.measure.is_some()) { - if camera.resized - || !scale_factor_events.is_empty() - || ui_scale.is_changed() - || node.is_changed() - || content_size - .as_ref() - .is_some_and(|c| c.is_changed() || c.measure.is_some()) - { - let layout_context = LayoutContext::new( - camera.scale_factor, - [camera.size.x as f32, camera.size.y as f32].into(), - ); - let measure = content_size.and_then(|mut c| c.measure.take()); - ui_surface.upsert_node(&layout_context, entity, &node, measure); - } - } else { - ui_surface.upsert_node(&LayoutContext::DEFAULT, entity, &Node::default(), None); + let layout_context = LayoutContext::new( + computed_target.scale_factor, + computed_target.physical_size.as_vec2(), + ); + let measure = content_size.and_then(|mut c| c.measure.take()); + ui_surface.upsert_node(&layout_context, entity, &node, measure); } }); - scale_factor_events.clear(); - - // clean up removed cameras - ui_surface.remove_camera_entities(removed_components.removed_cameras.read()); - - // update camera children - for (camera_id, _) in cameras.iter() { - let root_nodes = - if let Some(CameraLayoutInfo { root_nodes, .. }) = camera_layout_info.get(&camera_id) { - root_nodes.iter().cloned() - } else { - [].iter().cloned() - }; - ui_surface.set_camera_children(camera_id, root_nodes); - } // update and remove children - for entity in removed_components.removed_children.read() { + for entity in removed_children.read() { ui_surface.try_remove_children(entity); } @@ -264,11 +145,9 @@ with UI components as a child of an entity without UI components, your UI layout } }); - let text_buffers = &mut buffer_query; // clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used) ui_surface.remove_entities( - removed_components - .removed_nodes + removed_nodes .read() .filter(|entity| !node_query.contains(*entity)), ); @@ -280,28 +159,28 @@ with UI components as a child of an entity without UI components, your UI layout } }); - for (camera_id, mut camera) in camera_layout_info.drain() { - let inverse_target_scale_factor = camera.scale_factor.recip(); - - ui_surface.compute_camera_layout(camera_id, camera.size, text_buffers, &mut font_system); + for ui_root_entity in ui_root_node_query.iter() { + let (_, _, _, computed_target) = node_query.get(ui_root_entity).unwrap(); - for root in &camera.root_nodes { - update_uinode_geometry_recursive( - &mut commands, - *root, - &mut ui_surface, - true, - None, - &mut node_transform_query, - &ui_children, - inverse_target_scale_factor, - Vec2::ZERO, - Vec2::ZERO, - ); - } + ui_surface.compute_layout( + ui_root_entity, + computed_target.physical_size, + &mut buffer_query, + &mut font_system, + ); - camera.root_nodes.clear(); - interned_root_nodes.push(camera.root_nodes); + update_uinode_geometry_recursive( + &mut commands, + ui_root_entity, + &mut ui_surface, + true, + None, + &mut node_transform_query, + &ui_children, + computed_target.scale_factor.recip(), + Vec2::ZERO, + Vec2::ZERO, + ); } // Returns the combined bounding box of the node and any of its overflowing children. @@ -476,7 +355,7 @@ mod tests { use bevy_render::{camera::ManualTextureViews, prelude::Camera}; use bevy_transform::{ prelude::GlobalTransform, - systems::{propagate_transforms, sync_simple_transforms}, + systems::{compute_transform_leaves, propagate_parent_transforms, sync_simple_transforms}, }; use bevy_utils::prelude::default; use bevy_window::{ @@ -486,7 +365,7 @@ mod tests { use crate::{ layout::ui_surface::UiSurface, prelude::*, ui_layout_system, - update::update_target_camera_system, ContentSize, LayoutContext, + update::update_ui_context_system, ContentSize, LayoutContext, }; // these window dimensions are easy to convert to and from percentage values @@ -526,11 +405,12 @@ mod tests { ( // UI is driven by calculated camera target info, so we need to run the camera system first bevy_render::camera::camera_system, - update_target_camera_system, + update_ui_context_system, ApplyDeferred, ui_layout_system, sync_simple_transforms, - propagate_transforms, + propagate_parent_transforms, + compute_transform_leaves, ) .chain(), ); @@ -600,54 +480,6 @@ mod tests { assert!(ui_surface.entity_to_taffy.is_empty()); } - #[test] - fn ui_surface_tracks_camera_entities() { - let (mut world, mut ui_schedule) = setup_ui_test_world(); - - // despawn all cameras so we can reset ui_surface back to a fresh state - let camera_entities = world - .query_filtered::>() - .iter(&world) - .collect::>(); - for camera_entity in camera_entities { - world.despawn(camera_entity); - } - - ui_schedule.run(&mut world); - - // no UI entities in world, none in UiSurface - let ui_surface = world.resource::(); - assert!(ui_surface.camera_entity_to_taffy.is_empty()); - - // respawn camera - let camera_entity = world.spawn(Camera2d).id(); - - let ui_entity = world - .spawn((Node::default(), UiTargetCamera(camera_entity))) - .id(); - - // `ui_layout_system` should map `camera_entity` to a ui node in `UiSurface::camera_entity_to_taffy` - ui_schedule.run(&mut world); - - let ui_surface = world.resource::(); - assert!(ui_surface - .camera_entity_to_taffy - .contains_key(&camera_entity)); - assert_eq!(ui_surface.camera_entity_to_taffy.len(), 1); - - world.despawn(ui_entity); - world.despawn(camera_entity); - - // `ui_layout_system` should remove `camera_entity` from `UiSurface::camera_entity_to_taffy` - ui_schedule.run(&mut world); - - let ui_surface = world.resource::(); - assert!(!ui_surface - .camera_entity_to_taffy - .contains_key(&camera_entity)); - assert!(ui_surface.camera_entity_to_taffy.is_empty()); - } - #[test] #[should_panic] fn despawning_a_ui_entity_should_remove_its_corresponding_ui_node() { @@ -1119,7 +951,6 @@ mod tests { .get::() .unwrap() .iter() - .copied() .collect::>(); for r in [2, 3, 5, 7, 11, 13, 17, 19, 21, 23, 29, 31].map(|n| (n as f32).recip()) { @@ -1173,7 +1004,7 @@ mod tests { ( // UI is driven by calculated camera target info, so we need to run the camera system first bevy_render::camera::camera_system, - update_target_camera_system, + update_ui_context_system, ApplyDeferred, ui_layout_system, ) @@ -1207,11 +1038,9 @@ mod tests { let (mut world, ..) = setup_ui_test_world(); - let camera_entity = Entity::from_raw(0); let root_node_entity = Entity::from_raw(1); struct TestSystemParam { - camera_entity: Entity, root_node_entity: Entity, } @@ -1228,21 +1057,15 @@ mod tests { None, ); - ui_surface.compute_camera_layout( - params.camera_entity, + ui_surface.compute_layout( + params.root_node_entity, UVec2::new(800, 600), &mut computed_text_block_query, &mut font_system, ); } - let _ = world.run_system_once_with( - test_system, - TestSystemParam { - camera_entity, - root_node_entity, - }, - ); + let _ = world.run_system_once_with(test_system, TestSystemParam { root_node_entity }); let ui_surface = world.resource::(); diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs index 661924229efd8..e0333763b83d2 100644 --- a/crates/bevy_ui/src/layout/ui_surface.rs +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -13,14 +13,6 @@ use bevy_utils::default; use crate::{layout::convert, LayoutContext, LayoutError, Measure, MeasureArgs, Node, NodeMeasure}; use bevy_text::CosmicFontSystem; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RootNodePair { - // The implicit "viewport" node created by Bevy - pub(super) implicit_viewport_node: taffy::NodeId, - // The root (parentless) node specified by the user - pub(super) user_root_node: taffy::NodeId, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct LayoutNode { // Implicit "viewport" node if this `LayoutNode` corresponds to a root UI node entity @@ -40,9 +32,8 @@ impl From for LayoutNode { #[derive(Resource)] pub struct UiSurface { + pub root_entity_to_viewport_node: EntityHashMap, pub(super) entity_to_taffy: EntityHashMap, - pub(super) camera_entity_to_taffy: EntityHashMap>, - pub(super) camera_roots: EntityHashMap>, pub(super) taffy: TaffyTree, taffy_children_scratch: Vec, } @@ -50,8 +41,6 @@ pub struct UiSurface { fn _assert_send_sync_ui_surface_impl_safe() { fn _assert_send_sync() {} _assert_send_sync::>(); - _assert_send_sync::>>(); - _assert_send_sync::>>(); _assert_send_sync::>(); _assert_send_sync::(); } @@ -60,8 +49,6 @@ impl fmt::Debug for UiSurface { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("UiSurface") .field("entity_to_taffy", &self.entity_to_taffy) - .field("camera_entity_to_taffy", &self.camera_entity_to_taffy) - .field("camera_roots", &self.camera_roots) .field("taffy_children_scratch", &self.taffy_children_scratch) .finish() } @@ -71,9 +58,8 @@ impl Default for UiSurface { fn default() -> Self { let taffy: TaffyTree = TaffyTree::new(); Self { + root_entity_to_viewport_node: Default::default(), entity_to_taffy: Default::default(), - camera_entity_to_taffy: Default::default(), - camera_roots: Default::default(), taffy, taffy_children_scratch: Vec::new(), } @@ -166,127 +152,89 @@ impl UiSurface { } } - /// Sets the ui root node entities as children to the root node in the taffy layout. - pub fn set_camera_children( - &mut self, - camera_id: Entity, - children: impl Iterator, - ) { - let viewport_style = taffy::style::Style { - display: taffy::style::Display::Grid, - // Note: Taffy percentages are floats ranging from 0.0 to 1.0. - // So this is setting width:100% and height:100% - size: taffy::geometry::Size { - width: taffy::style::Dimension::Percent(1.0), - height: taffy::style::Dimension::Percent(1.0), - }, - align_items: Some(taffy::style::AlignItems::Start), - justify_items: Some(taffy::style::JustifyItems::Start), - ..default() - }; - - let camera_root_node_map = self.camera_entity_to_taffy.entry(camera_id).or_default(); - let existing_roots = self.camera_roots.entry(camera_id).or_default(); - let mut new_roots = Vec::new(); - for entity in children { - let node = self.entity_to_taffy.get_mut(&entity).unwrap(); - let root_node = existing_roots - .iter() - .find(|n| n.user_root_node == node.id) - .cloned() - .unwrap_or_else(|| { - if let Some(previous_parent) = self.taffy.parent(node.id) { - // remove the root node from the previous implicit node's children - self.taffy.remove_child(previous_parent, node.id).unwrap(); - } - - let viewport_node = *camera_root_node_map.entry(entity).or_insert_with(|| { - node.viewport_id - .unwrap_or_else(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()) - }); - node.viewport_id = Some(viewport_node); - self.taffy.add_child(viewport_node, node.id).unwrap(); - RootNodePair { - implicit_viewport_node: viewport_node, - user_root_node: node.id, - } - }); - new_roots.push(root_node); - } - - self.camera_roots.insert(camera_id, new_roots); + /// Gets or inserts an implicit taffy viewport node corresponding to the given UI root entity + pub fn get_or_insert_taffy_viewport_node(&mut self, ui_root_entity: Entity) -> taffy::NodeId { + *self + .root_entity_to_viewport_node + .entry(ui_root_entity) + .or_insert_with(|| { + let root_node = self.entity_to_taffy.get_mut(&ui_root_entity).unwrap(); + let implicit_root = self + .taffy + .new_leaf(taffy::style::Style { + display: taffy::style::Display::Grid, + // Note: Taffy percentages are floats ranging from 0.0 to 1.0. + // So this is setting width:100% and height:100% + size: taffy::geometry::Size { + width: taffy::style::Dimension::Percent(1.0), + height: taffy::style::Dimension::Percent(1.0), + }, + align_items: Some(taffy::style::AlignItems::Start), + justify_items: Some(taffy::style::JustifyItems::Start), + ..default() + }) + .unwrap(); + self.taffy.add_child(implicit_root, root_node.id).unwrap(); + root_node.viewport_id = Some(implicit_root); + implicit_root + }) } - /// Compute the layout for each window entity's corresponding root node in the layout. - pub fn compute_camera_layout<'a>( + /// Compute the layout for the given implicit taffy viewport node + pub fn compute_layout<'a>( &mut self, - camera: Entity, + ui_root_entity: Entity, render_target_resolution: UVec2, buffer_query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::ComputedTextBlock>, font_system: &'a mut CosmicFontSystem, ) { - let Some(camera_root_nodes) = self.camera_roots.get(&camera) else { - return; - }; + let implicit_viewport_node = self.get_or_insert_taffy_viewport_node(ui_root_entity); let available_space = taffy::geometry::Size { width: taffy::style::AvailableSpace::Definite(render_target_resolution.x as f32), height: taffy::style::AvailableSpace::Definite(render_target_resolution.y as f32), }; - for root_nodes in camera_root_nodes { - self.taffy - .compute_layout_with_measure( - root_nodes.implicit_viewport_node, - available_space, - |known_dimensions: taffy::Size>, - available_space: taffy::Size, - _node_id: taffy::NodeId, - context: Option<&mut NodeMeasure>, - style: &taffy::Style| - -> taffy::Size { - context - .map(|ctx| { - let buffer = get_text_buffer( - crate::widget::TextMeasure::needs_buffer( - known_dimensions.height, - available_space.width, - ), - ctx, - buffer_query, - ); - let size = ctx.measure( - MeasureArgs { - width: known_dimensions.width, - height: known_dimensions.height, - available_width: available_space.width, - available_height: available_space.height, - font_system, - buffer, - }, - style, - ); - taffy::Size { - width: size.x, - height: size.y, - } - }) - .unwrap_or(taffy::Size::ZERO) - }, - ) - .unwrap(); - } - } - /// Removes each camera entity from the internal map and then removes their associated node from taffy - pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { - for entity in entities { - if let Some(camera_root_node_map) = self.camera_entity_to_taffy.remove(&entity) { - for (entity, node) in camera_root_node_map.iter() { - self.taffy.remove(*node).unwrap(); - self.entity_to_taffy.get_mut(entity).unwrap().viewport_id = None; - } - } - } + self.taffy + .compute_layout_with_measure( + implicit_viewport_node, + available_space, + |known_dimensions: taffy::Size>, + available_space: taffy::Size, + _node_id: taffy::NodeId, + context: Option<&mut NodeMeasure>, + style: &taffy::Style| + -> taffy::Size { + context + .map(|ctx| { + let buffer = get_text_buffer( + crate::widget::TextMeasure::needs_buffer( + known_dimensions.height, + available_space.width, + ), + ctx, + buffer_query, + ); + let size = ctx.measure( + MeasureArgs { + width: known_dimensions.width, + height: known_dimensions.height, + available_width: available_space.width, + available_height: available_space.height, + font_system, + buffer, + }, + style, + ); + taffy::Size { + width: size.x, + height: size.y, + } + }) + .unwrap_or(taffy::Size::ZERO) + }, + ) + .unwrap(); } /// Removes each entity from the internal map and then removes their associated nodes from taffy @@ -335,7 +283,7 @@ impl UiSurface { } } -fn get_text_buffer<'a>( +pub fn get_text_buffer<'a>( needs_buffer: bool, ctx: &mut NodeMeasure, query: &'a mut bevy_ecs::prelude::Query<&mut bevy_text::ComputedTextBlock>, @@ -360,42 +308,16 @@ mod tests { use bevy_math::Vec2; use taffy::TraversePartialTree; - /// Checks if the parent of the `user_root_node` in a `RootNodePair` - /// is correctly assigned as the `implicit_viewport_node`. - fn is_root_node_pair_valid( - taffy_tree: &TaffyTree, - root_node_pair: &RootNodePair, - ) -> bool { - taffy_tree.parent(root_node_pair.user_root_node) - == Some(root_node_pair.implicit_viewport_node) - } - - /// Tries to get the root node pair for a given root node entity with the specified camera entity - fn get_root_node_pair_exact( - ui_surface: &UiSurface, - root_node_entity: Entity, - camera_entity: Entity, - ) -> Option<&RootNodePair> { - let root_node_pairs = ui_surface.camera_roots.get(&camera_entity)?; - let root_node_taffy = ui_surface.entity_to_taffy.get(&root_node_entity)?; - root_node_pairs - .iter() - .find(|&root_node_pair| root_node_pair.user_root_node == root_node_taffy.id) - } - #[test] fn test_initialization() { let ui_surface = UiSurface::default(); assert!(ui_surface.entity_to_taffy.is_empty()); - assert!(ui_surface.camera_entity_to_taffy.is_empty()); - assert!(ui_surface.camera_roots.is_empty()); assert_eq!(ui_surface.taffy.total_node_count(), 0); } #[test] fn test_upsert() { let mut ui_surface = UiSurface::default(); - let camera_entity = Entity::from_raw(0); let root_node_entity = Entity::from_raw(1); let node = Node::default(); @@ -413,188 +335,32 @@ mod tests { assert_eq!(ui_surface.taffy.total_node_count(), 1); // assign root node to camera - ui_surface.set_camera_children(camera_entity, vec![root_node_entity].into_iter()); + ui_surface.get_or_insert_taffy_viewport_node(root_node_entity); // each root node will create 2 taffy nodes assert_eq!(ui_surface.taffy.total_node_count(), 2); - // root node pair should now exist - let root_node_pair = get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity) - .expect("expected root node pair"); - assert!(is_root_node_pair_valid(&ui_surface.taffy, root_node_pair)); - // test duplicate insert 2 ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); // node count should not have increased assert_eq!(ui_surface.taffy.total_node_count(), 2); - - // root node pair should be unaffected - let root_node_pair = get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity) - .expect("expected root node pair"); - assert!(is_root_node_pair_valid(&ui_surface.taffy, root_node_pair)); } - #[test] - fn test_get_root_node_pair_exact() { - /// Attempts to find the camera entity that holds a reference to the given root node entity - fn get_associated_camera_entity( - ui_surface: &UiSurface, - root_node_entity: Entity, - ) -> Option { - for (&camera_entity, root_node_map) in ui_surface.camera_entity_to_taffy.iter() { - if root_node_map.contains_key(&root_node_entity) { - return Some(camera_entity); - } - } - None - } - - /// Attempts to find the root node pair corresponding to the given root node entity - fn get_root_node_pair( - ui_surface: &UiSurface, - root_node_entity: Entity, - ) -> Option<&RootNodePair> { - let camera_entity = get_associated_camera_entity(ui_surface, root_node_entity)?; - get_root_node_pair_exact(ui_surface, root_node_entity, camera_entity) - } - - let mut ui_surface = UiSurface::default(); - let camera_entity = Entity::from_raw(0); - let root_node_entity = Entity::from_raw(1); - let node = Node::default(); - - ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); - - // assign root node to camera - ui_surface.set_camera_children(camera_entity, [root_node_entity].into_iter()); - - assert_eq!( - get_associated_camera_entity(&ui_surface, root_node_entity), - Some(camera_entity) - ); - assert_eq!( - get_associated_camera_entity(&ui_surface, Entity::from_raw(2)), - None - ); - - let root_node_pair = get_root_node_pair(&ui_surface, root_node_entity); - assert!(root_node_pair.is_some()); - assert_eq!( - Some(root_node_pair.unwrap().user_root_node).as_ref(), - ui_surface - .entity_to_taffy - .get(&root_node_entity) - .map(|taffy_node| &taffy_node.id) - ); - - assert_eq!( - get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity), - root_node_pair - ); - } - - #[expect( - unreachable_code, - reason = "Certain pieces of code tested here cause the test to fail if made reachable; see #16362 for progress on fixing this" - )] - #[test] - fn test_remove_camera_entities() { - let mut ui_surface = UiSurface::default(); - let camera_entity = Entity::from_raw(0); - let root_node_entity = Entity::from_raw(1); - let node = Node::default(); - - ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); - - // assign root node to camera - ui_surface.set_camera_children(camera_entity, [root_node_entity].into_iter()); - - assert!(ui_surface - .camera_entity_to_taffy - .contains_key(&camera_entity)); - assert!(ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity)); - assert!(ui_surface.camera_roots.contains_key(&camera_entity)); - let root_node_pair = get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity) - .expect("expected root node pair"); - assert!(ui_surface - .camera_roots - .get(&camera_entity) - .unwrap() - .contains(root_node_pair)); - - ui_surface.remove_camera_entities([camera_entity]); - - // should not affect `entity_to_taffy` - assert!(ui_surface.entity_to_taffy.contains_key(&root_node_entity)); - - // `camera_roots` and `camera_entity_to_taffy` should no longer contain entries for `camera_entity` - assert!(!ui_surface - .camera_entity_to_taffy - .contains_key(&camera_entity)); - - return; // TODO: can't pass the test if we continue - not implemented (remove allow(unreachable_code)) - - assert!(!ui_surface.camera_roots.contains_key(&camera_entity)); - - // root node pair should be removed - let root_node_pair = get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity); - assert_eq!(root_node_pair, None); - } - - #[expect( - unreachable_code, - reason = "Certain pieces of code tested here cause the test to fail if made reachable; see #16362 for progress on fixing this" - )] #[test] fn test_remove_entities() { let mut ui_surface = UiSurface::default(); - let camera_entity = Entity::from_raw(0); let root_node_entity = Entity::from_raw(1); let node = Node::default(); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); - ui_surface.set_camera_children(camera_entity, [root_node_entity].into_iter()); + ui_surface.get_or_insert_taffy_viewport_node(root_node_entity); assert!(ui_surface.entity_to_taffy.contains_key(&root_node_entity)); - assert!(ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity)); - let root_node_pair = - get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity).unwrap(); - assert!(ui_surface - .camera_roots - .get(&camera_entity) - .unwrap() - .contains(root_node_pair)); ui_surface.remove_entities([root_node_entity]); assert!(!ui_surface.entity_to_taffy.contains_key(&root_node_entity)); - - return; // TODO: can't pass the test if we continue - not implemented (remove allow(unreachable_code)) - - assert!(!ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity)); - assert!(!ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity)); - assert!(ui_surface - .camera_roots - .get(&camera_entity) - .unwrap() - .is_empty()); } #[test] @@ -636,7 +402,6 @@ mod tests { #[test] fn test_set_camera_children() { let mut ui_surface = UiSurface::default(); - let camera_entity = Entity::from_raw(0); let root_node_entity = Entity::from_raw(1); let child_entity = Entity::from_raw(2); let node = Node::default(); @@ -653,28 +418,7 @@ mod tests { .add_child(root_taffy_node.id, child_taffy.id) .unwrap(); - ui_surface.set_camera_children(camera_entity, [root_node_entity].into_iter()); - - assert!( - ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity), - "root node not associated with camera" - ); - assert!( - !ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&child_entity), - "child of root node should not be associated with camera" - ); - - let _root_node_pair = - get_root_node_pair_exact(&ui_surface, root_node_entity, camera_entity) - .expect("expected root node pair"); + ui_surface.get_or_insert_taffy_viewport_node(root_node_entity); assert_eq!( ui_surface.taffy.parent(child_taffy.id), @@ -692,27 +436,10 @@ mod tests { ); // clear camera's root nodes - ui_surface.set_camera_children(camera_entity, Vec::::new().into_iter()); + ui_surface.get_or_insert_taffy_viewport_node(root_node_entity); return; // TODO: can't pass the test if we continue - not implemented (remove allow(unreachable_code)) - assert!( - !ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity), - "root node should have been unassociated with camera" - ); - assert!( - !ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&child_entity), - "child of root node should not be associated with camera" - ); - let root_taffy_children = ui_surface.taffy.children(root_taffy_node.id).unwrap(); assert!( root_taffy_children.contains(&child_taffy.id), @@ -724,25 +451,8 @@ mod tests { "expected root node child count to be 1" ); - // re-associate root node with camera - ui_surface.set_camera_children(camera_entity, vec![root_node_entity].into_iter()); - - assert!( - ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&root_node_entity), - "root node should have been re-associated with camera" - ); - assert!( - !ui_surface - .camera_entity_to_taffy - .get(&camera_entity) - .unwrap() - .contains_key(&child_entity), - "child of root node should not be associated with camera" - ); + // re-associate root node with viewport node + ui_surface.get_or_insert_taffy_viewport_node(root_node_entity); let child_taffy = ui_surface.entity_to_taffy.get(&child_entity).unwrap(); let root_taffy_children = ui_surface.taffy.children(root_taffy_node.id).unwrap(); diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index a5c0313e3453b..5dfb4feeebaf1 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -56,7 +56,7 @@ pub mod prelude { geometry::*, ui_material::*, ui_node::*, - widget::{Button, ImageNode, Label}, + widget::{Button, ImageNode, Label, NodeImageMode}, Interaction, MaterialNode, UiMaterialPlugin, UiScale, }, // `bevy_sprite` re-exports for texture slicing @@ -72,7 +72,7 @@ use bevy_transform::TransformSystem; use layout::ui_surface::UiSurface; use stack::ui_stack_system; pub use stack::UiStack; -use update::{update_clipping_system, update_target_camera_system}; +use update::{update_clipping_system, update_ui_context_system}; /// The basic plugin for Bevy UI pub struct UiPlugin { @@ -103,6 +103,8 @@ pub enum UiSystem { Focus, /// All UI systems in [`PostUpdate`] will run in or after this label. Prepare, + /// Update content requirements before layout. + Content, /// After this label, the ui layout state has been updated. /// /// Runs in [`PostUpdate`]. @@ -167,11 +169,13 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .configure_sets( PostUpdate, ( CameraUpdateSystem, - UiSystem::Prepare.before(UiSystem::Stack).after(Animation), + UiSystem::Prepare.after(Animation), + UiSystem::Content, UiSystem::Layout, UiSystem::PostLayout, ) @@ -194,7 +198,7 @@ impl Plugin for UiPlugin { app.add_systems( PostUpdate, ( - update_target_camera_system.in_set(UiSystem::Prepare), + update_ui_context_system.in_set(UiSystem::Prepare), ui_layout_system_config, ui_stack_system .in_set(UiSystem::Stack) @@ -208,7 +212,7 @@ impl Plugin for UiPlugin { // its own ImageNode, and `widget::text_system` & `bevy_text::update_text2d_layout` // will never modify a pre-existing `Image` asset. widget::update_image_content_size_system - .in_set(UiSystem::Prepare) + .in_set(UiSystem::Content) .in_set(AmbiguousWithTextSystem) .in_set(AmbiguousWithUpdateText2DLayout), ), @@ -260,7 +264,7 @@ fn build_text_interop(app: &mut App) { widget::measure_text_system, ) .chain() - .in_set(UiSystem::Prepare) + .in_set(UiSystem::Content) // Text and Text2d are independent. .ambiguous_with(bevy_text::detect_text_needs_rerender::) // Potential conflict: `Assets` @@ -273,6 +277,7 @@ fn build_text_interop(app: &mut App) { widget::text_system .in_set(UiSystem::PostLayout) .after(bevy_text::remove_dropped_font_atlas_sets) + .before(bevy_asset::AssetEvents) // Text2d and bevy_ui text are entirely on separate entities .ambiguous_with(bevy_text::detect_text_needs_rerender::) .ambiguous_with(bevy_text::update_text2d_layout) diff --git a/crates/bevy_ui/src/picking_backend.rs b/crates/bevy_ui/src/picking_backend.rs index 7f2b973b4e4c1..0a60cba1e5f7c 100644 --- a/crates/bevy_ui/src/picking_backend.rs +++ b/crates/bevy_ui/src/picking_backend.rs @@ -18,6 +18,9 @@ //! - `bevy_ui` can render on any camera with a flag, it is special, and is not tied to a particular //! camera. //! - To correctly sort picks, the order of `bevy_ui` is set to be the camera order plus 0.5. +//! - The `position` reported in `HitData` is normalized relative to the node, with `(0.,0.,0.)` at +//! the top left and `(1., 1., 0.)` in the bottom right. Coordinates are relative to the entire +//! node, not just the visible region. This backend does not provide a `normal`. #![deny(missing_docs)] @@ -51,7 +54,7 @@ pub struct NodeQuery { pickable: Option<&'static Pickable>, calculated_clip: Option<&'static CalculatedClip>, inherited_visibility: Option<&'static InheritedVisibility>, - target_camera: Option<&'static UiTargetCamera>, + target_camera: &'static ComputedNodeTarget, } /// Computes the UI node entities under each pointer. @@ -61,7 +64,6 @@ pub struct NodeQuery { pub fn ui_picking( pointers: Query<(&PointerId, &PointerLocation)>, camera_query: Query<(Entity, &Camera, Has)>, - default_ui_camera: DefaultUiCamera, primary_window: Query>, ui_stack: Res, node_query: Query, @@ -70,8 +72,6 @@ pub fn ui_picking( // For each camera, the pointer and its position let mut pointer_pos_by_camera = HashMap::>::default(); - let default_camera_entity = default_ui_camera.get(); - for (pointer_id, pointer_location) in pointers.iter().filter_map(|(pointer, pointer_location)| { Some(*pointer).zip(pointer_location.location().cloned()) @@ -107,7 +107,7 @@ pub fn ui_picking( } // The list of node entities hovered for each (camera, pointer) combo - let mut hit_nodes = HashMap::<(Entity, PointerId), Vec>::default(); + let mut hit_nodes = HashMap::<(Entity, PointerId), Vec<(Entity, Vec2)>>::default(); // prepare an iterator that contains all the nodes that have the cursor in their rect, // from the top node to the bottom one. this will also reset the interaction to `None` @@ -130,11 +130,7 @@ pub fn ui_picking( { continue; } - let Some(camera_entity) = node - .target_camera - .map(UiTargetCamera::entity) - .or(default_camera_entity) - else { + let Some(camera_entity) = node.target_camera.camera() else { continue; }; @@ -174,27 +170,28 @@ pub fn ui_picking( hit_nodes .entry((camera_entity, *pointer_id)) .or_default() - .push(*node_entity); + .push((*node_entity, relative_cursor_position)); } } } - for ((camera, pointer), hovered_nodes) in hit_nodes.iter() { + for ((camera, pointer), hovered) in hit_nodes.iter() { // As soon as a node with a `Block` focus policy is detected, the iteration will stop on it // because it "captures" the interaction. let mut picks = Vec::new(); let mut depth = 0.0; - for node in node_query.iter_many(hovered_nodes) { - let Some(camera_entity) = node - .target_camera - .map(UiTargetCamera::entity) - .or(default_camera_entity) - else { + for (hovered_node, position) in hovered { + let node = node_query.get(*hovered_node).unwrap(); + + let Some(camera_entity) = node.target_camera.camera() else { continue; }; - picks.push((node.entity, HitData::new(camera_entity, depth, None, None))); + picks.push(( + node.entity, + HitData::new(camera_entity, depth, Some(position.extend(0.0)), None), + )); if let Some(pickable) = node.pickable { // If an entity has a `Pickable` component, we will use that as the source of truth. @@ -215,6 +212,6 @@ pub fn ui_picking( .unwrap_or_default() as f32 + 0.5; // bevy ui can run on any camera, it's a special case - output.send(PointerHits::new(*pointer, picks, order)); + output.write(PointerHits::new(*pointer, picks, order)); } } diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index c9a32342f5606..0e501783564a4 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -3,8 +3,8 @@ use core::{hash::Hash, ops::Range}; use crate::{ - BoxShadow, BoxShadowSamples, CalculatedClip, ComputedNode, RenderUiSystem, - ResolvedBorderRadius, TransparentUi, UiTargetCamera, Val, + BoxShadow, BoxShadowSamples, CalculatedClip, ComputedNode, ComputedNodeTarget, RenderUiSystem, + ResolvedBorderRadius, TransparentUi, Val, }; use bevy_app::prelude::*; use bevy_asset::*; @@ -22,7 +22,6 @@ use bevy_math::{vec2, FloatOrd, Mat4, Rect, Vec2, Vec3Swizzles, Vec4Swizzles}; use bevy_render::sync_world::MainEntity; use bevy_render::RenderApp; use bevy_render::{ - camera::Camera, render_phase::*, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, @@ -35,7 +34,8 @@ use bytemuck::{Pod, Zeroable}; use super::{stack_z_offsets, UiCameraMap, UiCameraView, QUAD_INDICES, QUAD_VERTEX_POSITIONS}; -pub const BOX_SHADOW_SHADER_HANDLE: Handle = Handle::weak_from_u128(17717747047134343426); +pub const BOX_SHADOW_SHADER_HANDLE: Handle = + weak_handle!("d2991ecd-134f-4f82-adf5-0fcc86f02227"); /// A plugin that enables the rendering of box shadows. pub struct BoxShadowPlugin; @@ -236,7 +236,6 @@ pub struct ExtractedBoxShadows { pub fn extract_shadows( mut commands: Commands, mut extracted_box_shadows: ResMut, - camera_query: Extract>, box_shadow_query: Extract< Query<( Entity, @@ -245,12 +244,12 @@ pub fn extract_shadows( &InheritedVisibility, &BoxShadow, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, )>, >, camera_map: Extract, ) { - let mut camera_mapper = camera_map.get_mapper(); + let mut mapping = camera_map.get_mapper(); for (entity, uinode, transform, visibility, box_shadow, clip, camera) in &box_shadow_query { // Skip if no visible shadows @@ -258,18 +257,11 @@ pub fn extract_shadows( continue; } - let Some(extracted_camera_entity) = camera_mapper.map(camera) else { + let Some(extracted_camera_entity) = mapping.map(camera) else { continue; }; - let ui_physical_viewport_size = camera_query - .get(camera_mapper.current_camera()) - .ok() - .and_then(|(_, c)| { - c.physical_viewport_size() - .map(|size| Vec2::new(size.x as f32, size.y as f32)) - }) - .unwrap_or(Vec2::ZERO); + let ui_physical_viewport_size = camera.physical_size.as_vec2(); let scale_factor = uinode.inverse_scale_factor.recip(); @@ -385,10 +377,6 @@ pub fn queue_shadows( } } -#[expect( - clippy::too_many_arguments, - reason = "Could be rewritten with less arguments using a QueryData-implementing struct, but doesn't need to be." -)] pub fn prepare_shadows( mut commands: Commands, render_device: Res, diff --git a/crates/bevy_ui/src/render/debug_overlay.rs b/crates/bevy_ui/src/render/debug_overlay.rs index 67d527946a529..79001f3ba1982 100644 --- a/crates/bevy_ui/src/render/debug_overlay.rs +++ b/crates/bevy_ui/src/render/debug_overlay.rs @@ -1,6 +1,6 @@ +use crate::ui_node::ComputedNodeTarget; use crate::CalculatedClip; use crate::ComputedNode; -use crate::UiTargetCamera; use bevy_asset::AssetId; use bevy_color::Hsla; use bevy_ecs::entity::Entity; @@ -64,7 +64,7 @@ pub fn extract_debug_overlay( &InheritedVisibility, Option<&CalculatedClip>, &GlobalTransform, - Option<&UiTargetCamera>, + &ComputedNodeTarget, )>, >, camera_map: Extract, @@ -75,12 +75,12 @@ pub fn extract_debug_overlay( let mut camera_mapper = camera_map.get_mapper(); - for (entity, uinode, visibility, maybe_clip, transform, camera) in &uinode_query { + for (entity, uinode, visibility, maybe_clip, transform, computed_target) in &uinode_query { if !debug_options.show_hidden && !visibility.get() { continue; } - let Some(extracted_camera_entity) = camera_mapper.map(camera) else { + let Some(extracted_camera_entity) = camera_mapper.map(computed_target) else { continue; }; diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 6135ee739a7f5..f41c8f2b81620 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -9,11 +9,12 @@ mod debug_overlay; use crate::widget::ImageNode; use crate::{ - BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip, ComputedNode, DefaultUiCamera, - Outline, ResolvedBorderRadius, UiAntiAlias, UiTargetCamera, + BackgroundColor, BorderColor, BoxShadowSamples, CalculatedClip, ComputedNode, + ComputedNodeTarget, DefaultUiCamera, Outline, ResolvedBorderRadius, TextShadow, UiAntiAlias, + UiTargetCamera, }; use bevy_app::prelude::*; -use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle}; +use bevy_asset::{load_internal_asset, weak_handle, AssetEvent, AssetId, Assets, Handle}; use bevy_color::{Alpha, ColorToComponents, LinearRgba}; use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; @@ -95,7 +96,7 @@ pub mod stack_z_offsets { pub const MATERIAL: f32 = 0.18267; } -pub const UI_SHADER_HANDLE: Handle = Handle::weak_from_u128(13012847047162779583); +pub const UI_SHADER_HANDLE: Handle = weak_handle!("7d190d05-545b-42f5-bd85-22a0da85b0f6"); #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderUiSystem { @@ -105,6 +106,7 @@ pub enum RenderUiSystem { ExtractImages, ExtractTextureSlice, ExtractBorders, + ExtractTextShadows, ExtractText, ExtractDebug, } @@ -134,6 +136,7 @@ pub fn build_ui_render(app: &mut App) { RenderUiSystem::ExtractImages, RenderUiSystem::ExtractTextureSlice, RenderUiSystem::ExtractBorders, + RenderUiSystem::ExtractTextShadows, RenderUiSystem::ExtractText, RenderUiSystem::ExtractDebug, ) @@ -146,6 +149,7 @@ pub fn build_ui_render(app: &mut App) { 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), #[cfg(feature = "bevy_ui_debug")] debug_overlay::extract_debug_overlay.in_set(RenderUiSystem::ExtractDebug), @@ -254,17 +258,14 @@ impl ExtractedUiNodes { #[derive(SystemParam)] pub struct UiCameraMap<'w, 's> { - default: DefaultUiCamera<'w, 's>, mapping: Query<'w, 's, RenderEntity>, } impl<'w, 's> UiCameraMap<'w, 's> { /// Get the default camera and create the mapper pub fn get_mapper(&'w self) -> UiCameraMapper<'w, 's> { - let default_camera_entity = self.default.get(); UiCameraMapper { mapping: &self.mapping, - default_camera_entity, camera_entity: Entity::PLACEHOLDER, render_entity: Entity::PLACEHOLDER, } @@ -273,17 +274,14 @@ impl<'w, 's> UiCameraMap<'w, 's> { pub struct UiCameraMapper<'w, 's> { mapping: &'w Query<'w, 's, RenderEntity>, - default_camera_entity: Option, camera_entity: Entity, render_entity: Entity, } impl<'w, 's> UiCameraMapper<'w, 's> { /// Returns the render entity corresponding to the given `UiTargetCamera` or the default camera if `None`. - pub fn map(&mut self, camera: Option<&UiTargetCamera>) -> Option { - let camera_entity = camera - .map(UiTargetCamera::entity) - .or(self.default_camera_entity)?; + pub fn map(&mut self, computed_target: &ComputedNodeTarget) -> Option { + let camera_entity = computed_target.camera; if self.camera_entity != camera_entity { let Ok(new_render_camera_entity) = self.mapping.get(camera_entity) else { return None; @@ -335,7 +333,7 @@ pub fn extract_uinode_background_colors( &GlobalTransform, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, &BackgroundColor, )>, >, @@ -394,7 +392,7 @@ pub fn extract_uinode_images( &GlobalTransform, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, &ImageNode, )>, >, @@ -478,7 +476,7 @@ pub fn extract_uinode_borders( &GlobalTransform, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, AnyOf<(&BorderColor, &Outline)>, )>, >, @@ -494,7 +492,7 @@ pub fn extract_uinode_borders( global_transform, inherited_visibility, maybe_clip, - maybe_camera, + camera, (maybe_border_color, maybe_outline), ) in &uinode_query { @@ -503,7 +501,7 @@ pub fn extract_uinode_borders( continue; } - let Some(extracted_camera_entity) = camera_mapper.map(maybe_camera) else { + let Some(extracted_camera_entity) = camera_mapper.map(camera) else { continue; }; @@ -706,7 +704,7 @@ pub fn extract_text_sections( &GlobalTransform, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, &ComputedTextBlock, &TextLayoutInfo, )>, @@ -714,8 +712,8 @@ pub fn extract_text_sections( text_styles: Extract>, camera_map: Extract, ) { - let mut start = 0; - let mut end = 1; + let mut start = extracted_uinodes.glyphs.len(); + let mut end = start + 1; let mut camera_mapper = camera_map.get_mapper(); for ( @@ -741,8 +739,6 @@ pub fn extract_text_sections( let transform = global_transform.affine() * bevy_math::Affine3A::from_translation((-0.5 * uinode.size()).extend(0.)); - let mut color = LinearRgba::WHITE; - let mut current_span = usize::MAX; for ( i, PositionedGlyph { @@ -753,8 +749,20 @@ pub fn extract_text_sections( }, ) in text_layout_info.glyphs.iter().enumerate() { - if *span_index != current_span { - color = text_styles + let rect = texture_atlases + .get(&atlas_info.texture_atlas) + .unwrap() + .textures[atlas_info.location.glyph_index] + .as_rect(); + extracted_uinodes.glyphs.push(ExtractedGlyph { + transform: transform * Mat4::from_translation(position.extend(0.)), + rect, + }); + + if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { + info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture + }) { + let color = text_styles .get( computed_block .entities() @@ -764,9 +772,87 @@ pub fn extract_text_sections( ) .map(|text_color| LinearRgba::from(text_color.0)) .unwrap_or_default(); - current_span = *span_index; + extracted_uinodes.uinodes.push(ExtractedUiNode { + render_entity: commands.spawn(TemporaryRenderEntity).id(), + stack_index: uinode.stack_index, + color, + image: atlas_info.texture.id(), + clip: clip.map(|clip| clip.clip), + extracted_camera_entity, + rect, + item: ExtractedUiItem::Glyphs { range: start..end }, + main_entity: entity.into(), + }); + start = end; } + end += 1; + } + } +} + +pub fn extract_text_shadows( + mut commands: Commands, + mut extracted_uinodes: ResMut, + default_ui_camera: Extract, + texture_atlases: Extract>>, + uinode_query: Extract< + Query<( + Entity, + &ComputedNode, + &GlobalTransform, + &InheritedVisibility, + Option<&CalculatedClip>, + Option<&UiTargetCamera>, + &TextLayoutInfo, + &TextShadow, + )>, + >, + mapping: Extract>, +) { + let mut start = extracted_uinodes.glyphs.len(); + let mut end = start + 1; + + let default_ui_camera = default_ui_camera.get(); + for ( + entity, + uinode, + global_transform, + inherited_visibility, + clip, + camera, + text_layout_info, + shadow, + ) in &uinode_query + { + let Some(camera_entity) = camera.map(UiTargetCamera::entity).or(default_ui_camera) else { + continue; + }; + + // 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 Ok(extracted_camera_entity) = mapping.get(camera_entity) else { + continue; + }; + + let transform = global_transform.affine() + * Mat4::from_translation( + (-0.5 * uinode.size() + shadow.offset / uinode.inverse_scale_factor()).extend(0.), + ); + + for ( + i, + PositionedGlyph { + position, + atlas_info, + span_index, + .. + }, + ) in text_layout_info.glyphs.iter().enumerate() + { let rect = texture_atlases .get(&atlas_info.texture_atlas) .unwrap() @@ -778,12 +864,12 @@ pub fn extract_text_sections( }); if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { - info.span_index != current_span || info.atlas_info.texture != atlas_info.texture + info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture }) { extracted_uinodes.uinodes.push(ExtractedUiNode { render_entity: commands.spawn(TemporaryRenderEntity).id(), stack_index: uinode.stack_index, - color, + color: shadow.color.into(), image: atlas_info.texture.id(), clip: clip.map(|clip| clip.clip), extracted_camera_entity, diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 9a2505582e063..202f3749222f5 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -27,9 +27,11 @@ use bevy_sprite::BorderRect; use bevy_transform::prelude::GlobalTransform; use bytemuck::{Pod, Zeroable}; -pub const UI_MATERIAL_SHADER_HANDLE: Handle = Handle::weak_from_u128(10074188772096983955); +pub const UI_MATERIAL_SHADER_HANDLE: Handle = + weak_handle!("b5612b7b-aed5-41b4-a930-1d1588239fcd"); -const UI_VERTEX_OUTPUT_SHADER_HANDLE: Handle = Handle::weak_from_u128(10123618247720234751); +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). @@ -374,7 +376,7 @@ pub fn extract_ui_material_nodes( &MaterialNode, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, )>, >, camera_map: Extract, 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 53108ce16f859..fd9799b679149 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -30,7 +30,8 @@ use binding_types::{sampler, texture_2d}; use bytemuck::{Pod, Zeroable}; use widget::ImageNode; -pub const UI_SLICER_SHADER_HANDLE: Handle = Handle::weak_from_u128(11156288772117983964); +pub const UI_SLICER_SHADER_HANDLE: Handle = + weak_handle!("10cd61e3-bbf7-47fa-91c8-16cbe806378c"); pub struct UiTextureSlicerPlugin; @@ -255,7 +256,7 @@ pub fn extract_ui_texture_slices( &GlobalTransform, &InheritedVisibility, Option<&CalculatedClip>, - Option<&UiTargetCamera>, + &ComputedNodeTarget, &ImageNode, )>, >, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index f1e8c3a263173..96d9d2aeb2410 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2,11 +2,12 @@ use crate::{FocusPolicy, UiRect, Val}; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, system::SystemParam}; -use bevy_math::{vec4, Rect, Vec2, Vec4Swizzles}; +use bevy_math::{vec4, Rect, UVec2, Vec2, Vec4Swizzles}; use bevy_reflect::prelude::*; use bevy_render::{ camera::{Camera, RenderTarget}, view::Visibility, + view::VisibilityClass, }; use bevy_sprite::BorderRect; use bevy_transform::components::Transform; @@ -322,6 +323,7 @@ impl From for ScrollPosition { #[derive(Component, Clone, PartialEq, Debug, Reflect)] #[require( ComputedNode, + ComputedNodeTarget, BackgroundColor, BorderColor, BorderRadius, @@ -329,6 +331,7 @@ impl From for ScrollPosition { ScrollPosition, Transform, Visibility, + VisibilityClass, ZIndex )] #[reflect(Component, Default, PartialEq, Debug)] @@ -2762,3 +2765,60 @@ impl Default for BoxShadowSamples { Self(4) } } + +/// Derived information about the camera target for this UI node. +#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq)] +#[reflect(Component, Default)] +pub struct ComputedNodeTarget { + pub(crate) camera: Entity, + pub(crate) scale_factor: f32, + pub(crate) physical_size: UVec2, +} + +impl Default for ComputedNodeTarget { + fn default() -> Self { + Self { + camera: Entity::PLACEHOLDER, + scale_factor: 1., + physical_size: UVec2::ZERO, + } + } +} + +impl ComputedNodeTarget { + pub fn camera(&self) -> Option { + Some(self.camera).filter(|&entity| entity != Entity::PLACEHOLDER) + } + + pub const fn scale_factor(&self) -> f32 { + self.scale_factor + } + + pub const fn physical_size(&self) -> UVec2 { + self.physical_size + } + + pub fn logical_size(&self) -> Vec2 { + self.physical_size.as_vec2() / self.scale_factor + } +} + +/// Adds a shadow behind text +#[derive(Component, Copy, Clone, Debug, Reflect)] +#[reflect(Component, Default, Debug)] +pub struct TextShadow { + /// Shadow displacement in logical pixels + /// With a value of zero the shadow will be hidden directly behind the text + pub offset: Vec2, + /// Color of the shadow + pub color: Color, +} + +impl Default for TextShadow { + fn default() -> Self { + Self { + offset: Vec2::splat(4.), + color: Color::linear_rgba(0., 0., 0., 0.75), + } + } +} diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 390ddc6913b54..4c3b28a6923f9 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -2,17 +2,20 @@ use crate::{ experimental::{UiChildren, UiRootNodes}, - CalculatedClip, Display, Node, OverflowAxis, UiTargetCamera, + CalculatedClip, ComputedNodeTarget, DefaultUiCamera, Display, Node, OverflowAxis, UiScale, + UiTargetCamera, }; use super::ComputedNode; use bevy_ecs::{ - entity::Entity, + change_detection::DetectChangesMut, + entity::{hash_set::EntityHashSet, Entity}, + hierarchy::ChildOf, query::{Changed, With}, - system::{Commands, Query}, + system::{Commands, Local, Query, Res}, }; -use bevy_math::Rect; -use bevy_platform_support::collections::HashSet; +use bevy_math::{Rect, UVec2}; +use bevy_render::camera::Camera; use bevy_sprite::BorderRect; use bevy_transform::components::GlobalTransform; @@ -84,17 +87,10 @@ fn update_clipping( // Calculate new clip rectangle for children nodes let children_clip = if node.overflow.is_visible() { - // When `Visible`, children might be visible even when they are outside - // the current node's boundaries. In this case they inherit the current - // node's parent clip. If an ancestor is set as `Hidden`, that clip will - // be used; otherwise this will be `None`. + // The current node doesn't clip, propagate the optional inherited clipping rect to any children maybe_inherited_clip } else { - // If `maybe_inherited_clip` is `Some`, use the intersection between - // current node's clip and the inherited clip. This handles the case - // of nested `Overflow::Hidden` nodes. If parent `clip` is not - // defined, use the current node's clip. - + // Find the current node's clipping rect and intersect it with the inherited clipping rect, if one exists let mut clip_rect = Rect::from_center_size( global_transform.translation().truncate(), computed_node.size(), @@ -134,85 +130,520 @@ fn update_clipping( } } -pub fn update_target_camera_system( - mut commands: Commands, - changed_root_nodes_query: Query< - (Entity, Option<&UiTargetCamera>), - (With, Changed), - >, - node_query: Query<(Entity, Option<&UiTargetCamera>), With>, +pub fn update_ui_context_system( + default_ui_camera: DefaultUiCamera, + ui_scale: Res, + camera_query: Query<&Camera>, + target_camera_query: Query<&UiTargetCamera>, ui_root_nodes: UiRootNodes, + mut computed_target_query: Query<&mut ComputedNodeTarget>, ui_children: UiChildren, + reparented_nodes: Query<(Entity, &ChildOf), (Changed, With)>, + mut visited: Local, ) { - // Track updated entities to prevent redundant updates, as `Commands` changes are deferred, - // and updates done for changed_children_query can overlap with itself or with root_node_query - let mut updated_entities = >::default(); - - // Assuming that UiTargetCamera is manually set on the root node only, - // update root nodes first, since it implies the biggest change - for (root_node, target_camera) in changed_root_nodes_query.iter_many(ui_root_nodes.iter()) { - update_children_target_camera( - root_node, - target_camera, - &node_query, + visited.clear(); + let default_camera_entity = default_ui_camera.get(); + + for root_entity in ui_root_nodes.iter() { + let camera = target_camera_query + .get(root_entity) + .ok() + .map(UiTargetCamera::entity) + .or(default_camera_entity) + .unwrap_or(Entity::PLACEHOLDER); + + let (scale_factor, physical_size) = camera_query + .get(camera) + .ok() + .map(|camera| { + ( + camera.target_scaling_factor().unwrap_or(1.) * ui_scale.0, + camera.physical_viewport_size().unwrap_or(UVec2::ZERO), + ) + }) + .unwrap_or((1., UVec2::ZERO)); + + update_contexts_recursively( + root_entity, + ComputedNodeTarget { + camera, + scale_factor, + physical_size, + }, &ui_children, - &mut commands, - &mut updated_entities, + &mut computed_target_query, + &mut visited, ); } - // If the root node UiTargetCamera was changed, then every child is updated - // by this point, and iteration will be skipped. - // Otherwise, update changed children - for (parent, target_camera) in &node_query { - if !ui_children.is_changed(parent) { + for (entity, child_of) in reparented_nodes.iter() { + let Ok(computed_target) = computed_target_query.get(child_of.0) else { continue; - } + }; - update_children_target_camera( - parent, - target_camera, - &node_query, + update_contexts_recursively( + entity, + *computed_target, &ui_children, - &mut commands, - &mut updated_entities, + &mut computed_target_query, + &mut visited, ); } } -fn update_children_target_camera( +fn update_contexts_recursively( entity: Entity, - camera_to_set: Option<&UiTargetCamera>, - node_query: &Query<(Entity, Option<&UiTargetCamera>), With>, + inherited_computed_target: ComputedNodeTarget, ui_children: &UiChildren, - commands: &mut Commands, - updated_entities: &mut HashSet, + query: &mut Query<&mut ComputedNodeTarget>, + visited: &mut EntityHashSet, ) { - for child in ui_children.iter_ui_children(entity) { - // Skip if the child has already been updated or update is not needed - if updated_entities.contains(&child) - || camera_to_set == node_query.get(child).ok().and_then(|(_, camera)| camera) - { - continue; + 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, + ); } + } +} - match camera_to_set { - Some(camera) => { - commands.entity(child).try_insert(camera.clone()); - } - None => { - commands.entity(child).remove::(); +#[cfg(test)] +mod tests { + use bevy_asset::AssetEvent; + use bevy_asset::Assets; + use bevy_core_pipeline::core_2d::Camera2d; + use bevy_ecs::event::Events; + use bevy_ecs::hierarchy::ChildOf; + use bevy_ecs::schedule::IntoSystemConfigs; + use bevy_ecs::schedule::Schedule; + use bevy_ecs::world::World; + use bevy_image::Image; + use bevy_math::UVec2; + use bevy_render::camera::Camera; + use bevy_render::camera::ManualTextureViews; + use bevy_render::camera::RenderTarget; + use bevy_utils::default; + use bevy_window::PrimaryWindow; + use bevy_window::Window; + use bevy_window::WindowCreated; + use bevy_window::WindowRef; + use bevy_window::WindowResized; + use bevy_window::WindowResolution; + use bevy_window::WindowScaleFactorChanged; + + use crate::ComputedNodeTarget; + use crate::IsDefaultUiCamera; + use crate::Node; + use crate::UiScale; + use crate::UiTargetCamera; + + fn setup_test_world_and_schedule() -> (World, Schedule) { + let mut world = World::new(); + + world.init_resource::(); + + // init resources required by `camera_system` + world.init_resource::>(); + world.init_resource::>(); + world.init_resource::>(); + world.init_resource::>>(); + world.init_resource::>(); + world.init_resource::(); + + let mut schedule = Schedule::default(); + + schedule.add_systems( + ( + bevy_render::camera::camera_system, + super::update_ui_context_system, + ) + .chain(), + ); + + (world, schedule) + } + + #[test] + fn update_context_for_single_ui_root() { + let (mut world, mut schedule) = setup_test_world_and_schedule(); + + let scale_factor = 10.; + let physical_size = UVec2::new(1000, 500); + + world.spawn(( + Window { + resolution: WindowResolution::new(physical_size.x as f32, physical_size.y as f32) + .with_scale_factor_override(10.), + ..Default::default() + }, + PrimaryWindow, + )); + + let camera = world.spawn(Camera2d).id(); + + let uinode = world.spawn(Node::default()).id(); + + schedule.run(&mut world); + + assert_eq!( + *world.get::(uinode).unwrap(), + ComputedNodeTarget { + camera, + physical_size, + scale_factor, } + ); + } + + #[test] + fn update_multiple_context_for_multiple_ui_roots() { + let (mut world, mut schedule) = setup_test_world_and_schedule(); + + let scale1 = 1.; + let size1 = UVec2::new(100, 100); + let scale2 = 2.; + let size2 = UVec2::new(200, 200); + + world.spawn(( + Window { + resolution: WindowResolution::new(size1.x as f32, size1.y as f32) + .with_scale_factor_override(scale1), + ..Default::default() + }, + PrimaryWindow, + )); + + let window_2 = world + .spawn((Window { + resolution: WindowResolution::new(size2.x as f32, size2.y as f32) + .with_scale_factor_override(scale2), + ..Default::default() + },)) + .id(); + + let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id(); + let camera2 = world + .spawn(( + Camera2d, + Camera { + target: RenderTarget::Window(WindowRef::Entity(window_2)), + ..default() + }, + )) + .id(); + + let uinode1a = world.spawn(Node::default()).id(); + let uinode2a = world.spawn((Node::default(), UiTargetCamera(camera2))).id(); + let uinode2b = world.spawn((Node::default(), UiTargetCamera(camera2))).id(); + let uinode2c = world.spawn((Node::default(), UiTargetCamera(camera2))).id(); + let uinode1b = world.spawn(Node::default()).id(); + + schedule.run(&mut world); + + for (uinode, camera, scale_factor, physical_size) in [ + (uinode1a, camera1, scale1, size1), + (uinode1b, camera1, scale1, size1), + (uinode2a, camera2, scale2, size2), + (uinode2b, camera2, scale2, size2), + (uinode2c, camera2, scale2, size2), + ] { + assert_eq!( + *world.get::(uinode).unwrap(), + ComputedNodeTarget { + camera, + scale_factor, + physical_size, + } + ); } - updated_entities.insert(child); - - update_children_target_camera( - child, - camera_to_set, - node_query, - ui_children, - commands, - updated_entities, + } + + #[test] + fn update_context_on_changed_camera() { + let (mut world, mut schedule) = setup_test_world_and_schedule(); + + let scale1 = 1.; + let size1 = UVec2::new(100, 100); + let scale2 = 2.; + let size2 = UVec2::new(200, 200); + + world.spawn(( + Window { + resolution: WindowResolution::new(size1.x as f32, size1.y as f32) + .with_scale_factor_override(scale1), + ..Default::default() + }, + PrimaryWindow, + )); + + let window_2 = world + .spawn((Window { + resolution: WindowResolution::new(size2.x as f32, size2.y as f32) + .with_scale_factor_override(scale2), + ..Default::default() + },)) + .id(); + + let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id(); + let camera2 = world + .spawn(( + Camera2d, + Camera { + target: RenderTarget::Window(WindowRef::Entity(window_2)), + ..default() + }, + )) + .id(); + + let uinode = world.spawn(Node::default()).id(); + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .scale_factor, + scale1 + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .physical_size, + size1 + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .camera() + .unwrap(), + camera1 + ); + + world.entity_mut(uinode).insert(UiTargetCamera(camera2)); + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .scale_factor, + scale2 + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .physical_size, + size2 + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .camera() + .unwrap(), + camera2 + ); + } + + #[test] + fn update_context_after_parent_removed() { + let (mut world, mut schedule) = setup_test_world_and_schedule(); + + let scale1 = 1.; + let size1 = UVec2::new(100, 100); + let scale2 = 2.; + let size2 = UVec2::new(200, 200); + + world.spawn(( + Window { + resolution: WindowResolution::new(size1.x as f32, size1.y as f32) + .with_scale_factor_override(scale1), + ..Default::default() + }, + PrimaryWindow, + )); + + let window_2 = world + .spawn((Window { + resolution: WindowResolution::new(size2.x as f32, size2.y as f32) + .with_scale_factor_override(scale2), + ..Default::default() + },)) + .id(); + + let camera1 = world.spawn((Camera2d, IsDefaultUiCamera)).id(); + let camera2 = world + .spawn(( + Camera2d, + Camera { + target: RenderTarget::Window(WindowRef::Entity(window_2)), + ..default() + }, + )) + .id(); + + // `UiTargetCamera` is ignored on non-root UI nodes + let uinode1 = world.spawn((Node::default(), UiTargetCamera(camera2))).id(); + let uinode2 = world.spawn(Node::default()).add_child(uinode1).id(); + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .scale_factor(), + scale1 + ); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .physical_size(), + size1 + ); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .camera() + .unwrap(), + camera1 + ); + + assert_eq!( + world + .get::(uinode2) + .unwrap() + .camera() + .unwrap(), + camera1 + ); + + // Now `uinode1` is a root UI node its `UiTargetCamera` component will be used and its camera target set to `camera2`. + world.entity_mut(uinode1).remove::(); + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .scale_factor(), + scale2 + ); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .physical_size(), + size2 + ); + + assert_eq!( + world + .get::(uinode1) + .unwrap() + .camera() + .unwrap(), + camera2 + ); + + assert_eq!( + world + .get::(uinode2) + .unwrap() + .camera() + .unwrap(), + camera1 + ); + } + + #[test] + fn update_great_grandchild() { + let (mut world, mut schedule) = setup_test_world_and_schedule(); + + let scale = 1.; + let size = UVec2::new(100, 100); + + world.spawn(( + Window { + resolution: WindowResolution::new(size.x as f32, size.y as f32) + .with_scale_factor_override(scale), + ..Default::default() + }, + PrimaryWindow, + )); + + let camera = world.spawn(Camera2d).id(); + + let uinode = world.spawn(Node::default()).id(); + world.spawn(Node::default()).with_children(|builder| { + builder.spawn(Node::default()).with_children(|builder| { + builder.spawn(Node::default()).add_child(uinode); + }); + }); + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .scale_factor, + scale + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .physical_size, + size + ); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .camera() + .unwrap(), + camera + ); + + world.resource_mut::().0 = 2.; + + schedule.run(&mut world); + + assert_eq!( + world + .get::(uinode) + .unwrap() + .scale_factor(), + 2. ); } } diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index e34efdc715103..9c6abbbf82809 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,4 +1,4 @@ -use crate::{ContentSize, Measure, MeasureArgs, Node, NodeMeasure, UiScale}; +use crate::{ComputedNodeTarget, ContentSize, Measure, MeasureArgs, Node, NodeMeasure}; use bevy_asset::{Assets, Handle}; use bevy_color::Color; use bevy_ecs::prelude::*; @@ -7,7 +7,6 @@ use bevy_math::{Rect, UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE; use bevy_sprite::TextureSlicer; -use bevy_window::{PrimaryWindow, Window}; use taffy::{MaybeMath, MaybeResolve}; /// A UI Node that renders an image. @@ -254,21 +253,19 @@ type UpdateImageFilter = (With, Without); /// Updates content size of the node based on the image provided pub fn update_image_content_size_system( - mut previous_combined_scale_factor: Local, - windows: Query<&Window, With>, - ui_scale: Res, textures: Res>, - atlases: Res>, - mut query: Query<(&mut ContentSize, Ref, &mut ImageNodeSize), UpdateImageFilter>, + mut query: Query< + ( + &mut ContentSize, + Ref, + &mut ImageNodeSize, + Ref, + ), + UpdateImageFilter, + >, ) { - let combined_scale_factor = windows - .get_single() - .map(|window| window.resolution.scale_factor()) - .unwrap_or(1.) - * ui_scale.0; - - for (mut content_size, image, mut image_size) in &mut query { + for (mut content_size, image, mut image_size, computed_target) in &mut query { if !matches!(image.image_mode, NodeImageMode::Auto) || image.image.id() == TRANSPARENT_IMAGE_HANDLE.id() { @@ -289,18 +286,13 @@ pub fn update_image_content_size_system( }) { // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size - || combined_scale_factor != *previous_combined_scale_factor - || content_size.is_added() - { + if size != image_size.size || computed_target.is_changed() || content_size.is_added() { image_size.size = size; content_size.set(NodeMeasure::Image(ImageMeasure { // multiply the image size by the scale factor to get the physical size - size: size.as_vec2() * combined_scale_factor, + size: size.as_vec2() * computed_target.scale_factor(), })); } } } - - *previous_combined_scale_factor = combined_scale_factor; } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 9dec47103751d..e1be210950a0b 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -1,24 +1,22 @@ use crate::{ - ComputedNode, ContentSize, DefaultUiCamera, FixedMeasure, Measure, MeasureArgs, Node, - NodeMeasure, UiScale, UiTargetCamera, + ComputedNode, ComputedNodeTarget, ContentSize, FixedMeasure, Measure, MeasureArgs, Node, + NodeMeasure, }; use bevy_asset::Assets; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, - entity::{hash_map::EntityHashMap, Entity}, + entity::Entity, prelude::{require, Component}, query::With, reflect::ReflectComponent, - system::{Local, Query, Res, ResMut}, + system::{Query, Res, ResMut}, world::{Mut, Ref}, }; use bevy_image::prelude::*; use bevy_math::Vec2; -use bevy_platform_support::collections::hash_map::Entry; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::camera::Camera; use bevy_text::{ scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache, TextBounds, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextMeasureInfo, @@ -242,18 +240,13 @@ fn create_text_measure<'a>( /// A `Measure` is used by the UI's layout algorithm to determine the appropriate amount of space /// to provide for the text given the fonts, the text itself and the constraints of the layout. /// -/// * Measures are regenerated if the target camera's scale factor (or primary window if no specific target) or [`UiScale`] is changed. +/// * Measures are regenerated on changes to either [`ComputedTextBlock`] or [`ComputedNodeTarget`]. /// * Changes that only modify the colors of a `Text` do not require a new `Measure`. This system /// is only able to detect that a `Text` component has changed and will regenerate the `Measure` on /// color changes. This can be expensive, particularly for large blocks of text, and the [`bypass_change_detection`](bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection) /// method should be called when only changing the `Text`'s colors. pub fn measure_text_system( - mut scale_factors_buffer: Local>, - mut last_scale_factors: Local>, fonts: Res>, - camera_query: Query<&Camera>, - default_ui_camera: DefaultUiCamera, - ui_scale: Res, mut text_query: Query< ( Entity, @@ -261,7 +254,7 @@ pub fn measure_text_system( &mut ContentSize, &mut TextNodeFlags, &mut ComputedTextBlock, - Option<&UiTargetCamera>, + Ref, ), With, >, @@ -269,32 +262,9 @@ pub fn measure_text_system( mut text_pipeline: ResMut, mut font_system: ResMut, ) { - scale_factors_buffer.clear(); - - let default_camera_entity = default_ui_camera.get(); - - for (entity, block, content_size, text_flags, computed, maybe_camera) in &mut text_query { - let Some(camera_entity) = maybe_camera - .map(UiTargetCamera::entity) - .or(default_camera_entity) - else { - continue; - }; - - let scale_factor = match scale_factors_buffer.entry(camera_entity) { - Entry::Occupied(entry) => *entry.get(), - Entry::Vacant(entry) => *entry.insert( - camera_query - .get(camera_entity) - .ok() - .and_then(Camera::target_scaling_factor) - .unwrap_or(1.0) - * ui_scale.0, - ), - }; - + for (entity, block, content_size, text_flags, computed, computed_target) in &mut text_query { // Note: the ComputedTextBlock::needs_rerender bool is cleared in create_text_measure(). - if last_scale_factors.get(&camera_entity) != Some(&scale_factor) + if computed_target.is_changed() || computed.needs_rerender() || text_flags.needs_measure_fn || content_size.is_added() @@ -302,7 +272,7 @@ pub fn measure_text_system( create_text_measure( entity, &fonts, - scale_factor.into(), + computed_target.scale_factor.into(), text_reader.iter(entity), block, &mut text_pipeline, @@ -313,7 +283,6 @@ pub fn measure_text_system( ); } } - core::mem::swap(&mut *last_scale_factors, &mut *scale_factors_buffer); } #[inline] diff --git a/crates/bevy_utils/LICENSE-APACHE b/crates/bevy_utils/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_utils/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_utils/LICENSE-MIT b/crates/bevy_utils/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_utils/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_window/LICENSE-APACHE b/crates/bevy_window/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_window/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_window/LICENSE-MIT b/crates/bevy_window/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_window/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_window/src/system.rs b/crates/bevy_window/src/system.rs index c7ec83889740c..8cc6f020a5698 100644 --- a/crates/bevy_window/src/system.rs +++ b/crates/bevy_window/src/system.rs @@ -13,7 +13,7 @@ use bevy_ecs::prelude::*; pub fn exit_on_all_closed(mut app_exit_events: EventWriter, windows: Query<&Window>) { if windows.is_empty() { log::info!("No windows are open, exiting"); - app_exit_events.send(AppExit::Success); + app_exit_events.write(AppExit::Success); } } @@ -28,7 +28,7 @@ pub fn exit_on_primary_closed( ) { if windows.is_empty() { log::info!("Primary window was closed, exiting"); - app_exit_events.send(AppExit::Success); + app_exit_events.write(AppExit::Success); } } diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 3e215d6e6a540..9df34c2142d7d 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -58,7 +58,7 @@ cfg-if = "1.0" raw-window-handle = "0.6" serde = { version = "1.0", features = ["derive"], optional = true } bytemuck = { version = "1.5", optional = true } -wgpu-types = { version = "23", optional = true } +wgpu-types = { version = "24", optional = true } accesskit = "0.17" tracing = { version = "0.1", default-features = false, features = ["std"] } diff --git a/crates/bevy_winit/LICENSE-APACHE b/crates/bevy_winit/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_winit/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_winit/LICENSE-MIT b/crates/bevy_winit/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_winit/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_winit/src/accessibility.rs b/crates/bevy_winit/src/accessibility.rs index 4a4e717e3d45e..0b5b1e6b00b55 100644 --- a/crates/bevy_winit/src/accessibility.rs +++ b/crates/bevy_winit/src/accessibility.rs @@ -159,7 +159,7 @@ fn poll_receivers( for (_id, handler) in handlers.iter() { let mut handler = handler.lock().unwrap(); while let Some(event) = handler.pop_front() { - actions.send(ActionRequestWrapper(event)); + actions.write(ActionRequestWrapper(event)); } } } diff --git a/crates/bevy_winit/src/custom_cursor.rs b/crates/bevy_winit/src/custom_cursor.rs index 6d3d804dd3916..f6c90da6e20b8 100644 --- a/crates/bevy_winit/src/custom_cursor.rs +++ b/crates/bevy_winit/src/custom_cursor.rs @@ -145,18 +145,16 @@ pub(crate) fn extract_rgba_pixels(image: &Image) -> Option> { | TextureFormat::Rgba8UnormSrgb | TextureFormat::Rgba8Snorm | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint => Some(image.data.clone()), - TextureFormat::Rgba32Float => Some( - image - .data - .chunks(4) + | TextureFormat::Rgba8Sint => Some(image.data.clone()?), + TextureFormat::Rgba32Float => image.data.as_ref().map(|data| { + data.chunks(4) .map(|chunk| { let chunk = chunk.try_into().unwrap(); let num = bytemuck::cast_ref::<[u8; 4], f32>(chunk); ops::round(num.clamp(0.0, 1.0) * 255.0) as u8 }) - .collect(), - ), + .collect() + }), _ => None, } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index ab479f8182f03..83ee3013d7ded 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -907,7 +907,7 @@ pub(crate) fn react_to_resize( .resolution .set_physical_resolution(size.width, size.height); - window_resized.send(WindowResized { + window_resized.write(WindowResized { window: window_entity, width: window.width(), height: window.height(), @@ -923,7 +923,7 @@ pub(crate) fn react_to_scale_factor_change( ) { window.resolution.set_scale_factor(scale_factor as f32); - window_backend_scale_factor_changed.send(WindowBackendScaleFactorChanged { + window_backend_scale_factor_changed.write(WindowBackendScaleFactorChanged { window: window_entity, scale_factor, }); @@ -932,7 +932,7 @@ pub(crate) fn react_to_scale_factor_change( let scale_factor_override = window.resolution.scale_factor_override(); if scale_factor_override.is_none() && !relative_eq!(scale_factor as f32, prior_factor) { - window_scale_factor_changed.send(WindowScaleFactorChanged { + window_scale_factor_changed.write(WindowScaleFactorChanged { window: window_entity, scale_factor, }); diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 2c481f960878c..49e73bceae471 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -115,7 +115,7 @@ pub fn create_windows( } } - window_created_events.send(WindowCreated { window: entity }); + window_created_events.write(WindowCreated { window: entity }); } } @@ -135,7 +135,7 @@ pub(crate) fn check_keyboard_focus_lost( } } if focus_lost & !focus_gained { - keyboard_focus.send(KeyboardFocusLost); + keyboard_focus.write(KeyboardFocusLost); } } @@ -215,7 +215,7 @@ pub(crate) fn despawn_windows( // Drop all the windows that are waiting to be closed windows_to_drop.clear(); for window in closing.iter() { - closing_events.send(WindowClosing { window }); + closing_events.write(WindowClosing { window }); } for window in closed.read() { info!("Closing window {}", window); @@ -230,7 +230,7 @@ pub(crate) fn despawn_windows( // 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.send(WindowClosed { window }); + closed_events.write(WindowClosed { window }); } } @@ -239,7 +239,7 @@ pub(crate) fn despawn_windows( if !exit_events.is_empty() { exit_events.clear(); for window in window_entities.iter() { - closing_events.send(WindowClosing { window }); + closing_events.write(WindowClosing { window }); } } } diff --git a/deny.toml b/deny.toml index f8114fed1d1a7..5373c938bc421 100644 --- a/deny.toml +++ b/deny.toml @@ -3,10 +3,7 @@ all-features = true [advisories] version = 2 -ignore = [ - # TODO: #16477 - Delete this once notify-types has been bumped. - "RUSTSEC-2024-0384", -] +ignore = [] [licenses] version = 2 @@ -78,6 +75,9 @@ deny = [ { name = "glam", deny-multiple-versions = true }, { name = "raw-window-handle", deny-multiple-versions = true }, ] +skip = [ + { name = "bevy_math", reason = "bevy_math has a path dev dependency on itself without a version" }, +] [sources] unknown-registry = "deny" diff --git a/docs-rs/README.md b/docs-rs/README.md new file mode 100644 index 0000000000000..5d9092b806185 --- /dev/null +++ b/docs-rs/README.md @@ -0,0 +1,33 @@ +# Docs.rs Extensions + +This directory includes some templates and styling to extend and modify [rustdoc]'s output +for Bevy's documentation on [docs.rs]. Currently this consists of tags indicating core +`bevy_ecs` traits. + +## 3rd Party Crates + +To use in your own crate, first copy this folder into your project, +then add the following to your Cargo.toml: + +```toml +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs_dep"] +rustdoc-args = [ + "--cfg", "docsrs_dep", + "--html-after-content", "docs-rs/trait-tags.html", +] + +[lints.rust] +unexpected_cfgs = { check-cfg = ['cfg(docsrs_dep)'] } +``` + +## Local Testing + +Build the documentation with the extension enabled like this: + +```bash +RUSTDOCFLAGS="--html-after-content docs-rs/trait-tags.html --cfg docsrs_dep" RUSTFLAGS="--cfg docsrs_dep" cargo doc --no-deps --package +``` + +[rustdoc]: https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html +[docs.rs]: https://docs.rs diff --git a/docs-rs/trait-tags.html b/docs-rs/trait-tags.html new file mode 100644 index 0000000000000..3f65362213921 --- /dev/null +++ b/docs-rs/trait-tags.html @@ -0,0 +1,165 @@ + + + \ No newline at end of file diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 5fcca5a6003fa..f26c74ceca871 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -79,7 +79,6 @@ The default feature set enables most of the expected features of a game engine, |gif|GIF image format support| |glam_assert|Enable assertions to check the validity of parameters passed to glam| |ico|ICO image format support| -|ios_simulator|Enable support for the ios_simulator by downgrading some rendering capabilities| |jpeg|JPEG image format support| |meshlet|Enables the meshlet renderer for dense high-poly scenes (experimental)| |meshlet_processor|Enables processing meshes into meshlet meshes for bevy_pbr| @@ -91,11 +90,13 @@ The default feature set enables most of the expected features of a game engine, |pbr_transmission_textures|Enable support for transmission-related textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs| |pnm|PNM image format support, includes pam, pbm, pgm and ppm| |qoi|QOI image format support| +|reflect_documentation|Enable documentation reflection| |reflect_functions|Enable function reflection| |serialize|Enable serialization support through serde| |shader_format_glsl|Enable support for shaders in GLSL| |shader_format_spirv|Enable support for shaders in SPIR-V| |spirv_shader_passthrough|Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)| +|statically-linked-dxc|Statically linked DXC shader compiler for DirectX 12| |symphonia-aac|AAC audio format support (through symphonia)| |symphonia-all|AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)| |symphonia-flac|FLAC audio format support (through symphonia)| diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index 0fc22fa13f845..fd90210d6fbc7 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -73,16 +73,16 @@ fn setup( // ------------------------------------------------------------------------------------------------ fn update_bloom_settings( - camera: Single<(Entity, Option<&mut Bloom>), With>, + camera: Single<(Entity, &Tonemapping, Option<&mut Bloom>), With>, mut text: Single<&mut Text>, mut commands: Commands, keycode: Res>, time: Res