diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 500eb34bb9..952877b9f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.24.3 + uses: crate-ci/typos@v1.24.5 - name: Typos info if: failure() run: | diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 1f30fe2428..7902584a9f 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -49,7 +49,7 @@ jobs: --exclude build-wasm-example - name: Create PR - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: delete-branch: true base: "main" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4efe748bcf..32e481b230 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: --exclude build-wasm-example - name: Create PR - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: delete-branch: true base: "main" diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index 23bcbcf8be..96a287981d 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -11,6 +11,8 @@ on: jobs: welcome: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - uses: actions/github-script@v7 with: diff --git a/.gitignore b/.gitignore index 2fab476252..10507748f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ Cargo.lock .cargo/config.toml /.idea /.vscode +.zed /benches/target /tools/compile_fail_utils/target dxcompiler.dll diff --git a/Cargo.toml b/Cargo.toml index 0a0ea25aab..5242ee4172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" repository = "https://github.com/bevyengine/bevy" documentation = "https://docs.rs/bevy" -rust-version = "1.79.0" +rust-version = "1.80.0" [workspace] exclude = [ @@ -3244,6 +3244,28 @@ description = "A first-person camera that uses a world model and a view model wi category = "Camera" wasm = true +[[example]] +name = "projection_zoom" +path = "examples/camera/projection_zoom.rs" +doc-scrape-examples = true + +[package.metadata.example.projection_zoom] +name = "Projection Zoom" +description = "Shows how to zoom orthographic and perspective projection cameras." +category = "Camera" +wasm = true + +[[example]] +name = "camera_orbit" +path = "examples/camera/camera_orbit.rs" +doc-scrape-examples = true + +[package.metadata.example.camera_orbit] +name = "Camera Orbit" +description = "Shows how to orbit a static scene using pitch, yaw, and roll." +category = "Camera" +wasm = true + [package.metadata.example.fps_overlay] name = "FPS overlay" description = "Demonstrates FPS overlay" @@ -3430,6 +3452,17 @@ description = "Demonstrates animation masks" category = "Animation" wasm = true +[[example]] +name = "pcss" +path = "examples/3d/pcss.rs" +doc-scrape-examples = true + +[package.metadata.example.pcss] +name = "Percentage-closer soft shadows" +description = "Demonstrates percentage-closer soft shadows (PCSS)" +category = "3D Rendering" +wasm = false + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/assets/environment_maps/sky_skybox.ktx2 b/assets/environment_maps/sky_skybox.ktx2 new file mode 100644 index 0000000000..d386497ac1 Binary files /dev/null and b/assets/environment_maps/sky_skybox.ktx2 differ diff --git a/assets/models/PalmTree/PalmTree.bin b/assets/models/PalmTree/PalmTree.bin new file mode 100644 index 0000000000..614c4d2968 Binary files /dev/null and b/assets/models/PalmTree/PalmTree.bin differ diff --git a/assets/models/PalmTree/PalmTree.gltf b/assets/models/PalmTree/PalmTree.gltf new file mode 100644 index 0000000000..c4987e1ea6 --- /dev/null +++ b/assets/models/PalmTree/PalmTree.gltf @@ -0,0 +1,1066 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.1.63", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 3, + 6, + 9, + 12, + 15, + 18, + 21, + 22, + 23 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"B\u00e9zierCurve", + "rotation":[ + -0.6492608785629272, + 0.6492608189582825, + -0.28010788559913635, + 0.28010791540145874 + ], + "scale":[ + -1.5591872930526733, + -1.5591872930526733, + -1.5591872930526733 + ], + "translation":[ + 0.7588800191879272, + 1.8171958923339844, + -0.701636791229248 + ] + }, + { + "name":"B\u00e9zierCurve.001" + }, + { + "mesh":1, + "name":"Grid", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551956415176392, + 0.06293392181396484, + -0.1808854639530182 + ] + }, + { + "children":[ + 1, + 2 + ], + "name":"Empty", + "rotation":[ + 0, + -0.16959351301193237, + 0, + 0.9855141043663025 + ], + "translation":[ + 0.27010849118232727, + 3.3713648319244385, + -0.5507277250289917 + ] + }, + { + "name":"B\u00e9zierCurve.002" + }, + { + "mesh":2, + "name":"Grid.001", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551955819129944, + 0.06293395161628723, + -0.18088552355766296 + ] + }, + { + "children":[ + 4, + 5 + ], + "name":"Empty.001", + "rotation":[ + -0.1744275838136673, + -0.9500948786735535, + -0.04670312628149986, + 0.2543886601924896 + ], + "translation":[ + -0.32273390889167786, + 3.377293348312378, + -0.6218688488006592 + ] + }, + { + "name":"B\u00e9zierCurve.003" + }, + { + "mesh":3, + "name":"Grid.002", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551957011222839, + 0.06293392181396484, + -0.18088555335998535 + ] + }, + { + "children":[ + 7, + 8 + ], + "name":"Empty.002", + "rotation":[ + 0, + 0.9468244314193726, + 0, + 0.3217506408691406 + ], + "translation":[ + -0.10338221490383148, + 3.377293348312378, + -0.7404372692108154 + ] + }, + { + "name":"B\u00e9zierCurve.004" + }, + { + "mesh":4, + "name":"Grid.003", + "scale":[ + 0.7174922823905945, + 1, + 1 + ], + "translation":[ + 0.8551957011222839, + 0.0629342570900917, + -0.18088553845882416 + ] + }, + { + "children":[ + 10, + 11 + ], + "name":"Empty.003", + "rotation":[ + 0.039769601076841354, + -0.5909609794616699, + -0.054099712520837784, + 0.803900957107544 + ], + "translation":[ + -0.020384281873703003, + 3.377293348312378, + -0.3432328999042511 + ] + }, + { + "name":"B\u00e9zierCurve.005" + }, + { + "mesh":5, + "name":"Grid.004", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551955819129944, + 0.0629342794418335, + -0.18088550865650177 + ] + }, + { + "children":[ + 13, + 14 + ], + "name":"Empty.004", + "rotation":[ + 0.06433407217264175, + 0.6805833578109741, + 0.06868407875299454, + 0.7266016602516174 + ], + "translation":[ + 0.14561158418655396, + 3.377293348312378, + -0.633725643157959 + ] + }, + { + "name":"B\u00e9zierCurve.006" + }, + { + "mesh":6, + "name":"Grid.005", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551957607269287, + 0.06293407082557678, + -0.18088555335998535 + ] + }, + { + "children":[ + 16, + 17 + ], + "name":"Empty.005", + "rotation":[ + -0.027264947071671486, + 0.32132646441459656, + -0.08003053814172745, + 0.9431866407394409 + ], + "translation":[ + 0.14561158418655396, + 3.377293348312378, + -0.633725643157959 + ] + }, + { + "name":"B\u00e9zierCurve.007" + }, + { + "mesh":7, + "name":"Grid.006", + "scale":[ + 0.7174922227859497, + 1, + 1 + ], + "translation":[ + 0.8551956415176392, + 0.06293423473834991, + -0.18088550865650177 + ] + }, + { + "children":[ + 19, + 20 + ], + "name":"Empty.006", + "rotation":[ + 0.025538405403494835, + -0.8604785799980164, + -0.015095553360879421, + 0.5086221694946289 + ], + "translation":[ + -0.13840311765670776, + 3.38228178024292, + -0.48537585139274597 + ] + }, + { + "mesh":8, + "name":"Landscape", + "rotation":[ + 0, + -0.07845905423164368, + 0, + 0.9969173669815063 + ], + "scale":[ + 1.0773953199386597, + 1.0773954391479492, + 1.0773953199386597 + ], + "translation":[ + -1.4325428009033203, + 0.049118101596832275, + -17.66829490661621 + ] + }, + { + "mesh":9, + "name":"Landscape_plane", + "scale":[ + 6.12558650970459, + 6.12558650970459, + 6.12558650970459 + ] + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Trunk", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.61811763048172, + 0.26356762647628784, + 0.11393062770366669, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Leaves", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.2105390429496765, + 0.8000074625015259, + 0.14856106042861938, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 1, + 0.9668273329734802, + 0.5682248473167419, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "emissiveFactor":[ + 1, + 1, + 1 + ], + "emissiveTexture":{ + "index":0 + }, + "name":"Water", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":1 + }, + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"B\u00e9zierCurve", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"Grid", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.001", + "primitives":[ + { + "attributes":{ + "POSITION":8, + "NORMAL":9, + "TEXCOORD_0":10 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.002", + "primitives":[ + { + "attributes":{ + "POSITION":11, + "NORMAL":12, + "TEXCOORD_0":13 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.003", + "primitives":[ + { + "attributes":{ + "POSITION":14, + "NORMAL":15, + "TEXCOORD_0":16 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.004", + "primitives":[ + { + "attributes":{ + "POSITION":17, + "NORMAL":18, + "TEXCOORD_0":19 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.005", + "primitives":[ + { + "attributes":{ + "POSITION":20, + "NORMAL":21, + "TEXCOORD_0":22 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Grid.006", + "primitives":[ + { + "attributes":{ + "POSITION":23, + "NORMAL":24, + "TEXCOORD_0":25 + }, + "indices":7, + "material":1 + } + ] + }, + { + "name":"Landscape.001", + "primitives":[ + { + "attributes":{ + "POSITION":26, + "NORMAL":27, + "TEXCOORD_0":28 + }, + "indices":29, + "material":2 + } + ] + }, + { + "name":"Landscape_plane", + "primitives":[ + { + "attributes":{ + "POSITION":30, + "NORMAL":31, + "TEXCOORD_0":32 + }, + "indices":33, + "material":3 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"StylizedWater", + "uri":"StylizedWater.png" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":720, + "max":[ + 1.0449047088623047, + 0.10000000149011612, + 0.4650161862373352 + ], + "min":[ + -1.0722216367721558, + -0.10000000149011612, + -0.10050036013126373 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":720, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":720, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":1260, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":150, + "max":[ + 0.6420668959617615, + 0.27458858489990234, + 0.5718027353286743 + ], + "min":[ + -1.78363037109375, + -0.2425653040409088, + -0.18408852815628052 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5123, + "count":756, + "type":"SCALAR" + }, + { + "bufferView":8, + "componentType":5126, + "count":150, + "max":[ + 0.6420671343803406, + 0.2745886743068695, + 0.5718027949333191 + ], + "min":[ + -1.7836307287216187, + -0.24256540834903717, + -0.18408851325511932 + ], + "type":"VEC3" + }, + { + "bufferView":9, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":10, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":11, + "componentType":5126, + "count":150, + "max":[ + 0.6420671343803406, + 0.27458858489990234, + 0.5718027949333191 + ], + "min":[ + -1.783630609512329, + -0.2425653636455536, + -0.18408846855163574 + ], + "type":"VEC3" + }, + { + "bufferView":12, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":13, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":14, + "componentType":5126, + "count":150, + "max":[ + 0.6420667767524719, + 0.2745886743068695, + 0.5718027949333191 + ], + "min":[ + -1.783630609512329, + -0.24256554245948792, + -0.18408846855163574 + ], + "type":"VEC3" + }, + { + "bufferView":15, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":16, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":17, + "componentType":5126, + "count":150, + "max":[ + 0.6420665383338928, + 0.2745887041091919, + 0.5718027353286743 + ], + "min":[ + -1.78363037109375, + -0.24256561696529388, + -0.1840885579586029 + ], + "type":"VEC3" + }, + { + "bufferView":18, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":19, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":20, + "componentType":5126, + "count":150, + "max":[ + 0.6420668959617615, + 0.27458861470222473, + 0.5718027949333191 + ], + "min":[ + -1.783630609512329, + -0.24256546795368195, + -0.18408843874931335 + ], + "type":"VEC3" + }, + { + "bufferView":21, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":22, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":23, + "componentType":5126, + "count":150, + "max":[ + 0.642067015171051, + 0.2745887339115143, + 0.5718027353286743 + ], + "min":[ + -1.78363037109375, + -0.24256548285484314, + -0.1840885430574417 + ], + "type":"VEC3" + }, + { + "bufferView":24, + "componentType":5126, + "count":150, + "type":"VEC3" + }, + { + "bufferView":25, + "componentType":5126, + "count":150, + "type":"VEC2" + }, + { + "bufferView":26, + "componentType":5126, + "count":4096, + "max":[ + 32, + 1, + 32 + ], + "min":[ + -32, + -1, + -32 + ], + "type":"VEC3" + }, + { + "bufferView":27, + "componentType":5126, + "count":4096, + "type":"VEC3" + }, + { + "bufferView":28, + "componentType":5126, + "count":4096, + "type":"VEC2" + }, + { + "bufferView":29, + "componentType":5123, + "count":23814, + "type":"SCALAR" + }, + { + "bufferView":30, + "componentType":5126, + "count":4, + "max":[ + 32, + 0.009999999776482582, + 32 + ], + "min":[ + -32, + 0.009999999776482582, + -32 + ], + "type":"VEC3" + }, + { + "bufferView":31, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":32, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":33, + "componentType":5123, + "count":6, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":8640, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":8640, + "byteOffset":8640, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":17280, + "target":34962 + }, + { + "buffer":0, + "byteLength":2520, + "byteOffset":23040, + "target":34963 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":25560, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":27360, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":29160, + "target":34962 + }, + { + "buffer":0, + "byteLength":1512, + "byteOffset":30360, + "target":34963 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":31872, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":33672, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":35472, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":36672, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":38472, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":40272, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":41472, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":43272, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":45072, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":46272, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":48072, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":49872, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":51072, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":52872, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":54672, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":55872, + "target":34962 + }, + { + "buffer":0, + "byteLength":1800, + "byteOffset":57672, + "target":34962 + }, + { + "buffer":0, + "byteLength":1200, + "byteOffset":59472, + "target":34962 + }, + { + "buffer":0, + "byteLength":49152, + "byteOffset":60672, + "target":34962 + }, + { + "buffer":0, + "byteLength":49152, + "byteOffset":109824, + "target":34962 + }, + { + "buffer":0, + "byteLength":32768, + "byteOffset":158976, + "target":34962 + }, + { + "buffer":0, + "byteLength":47628, + "byteOffset":191744, + "target":34963 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":239372, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":239420, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":239468, + "target":34962 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":239500, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":239512, + "uri":"PalmTree.bin" + } + ] +} diff --git a/assets/models/PalmTree/StylizedWater.png b/assets/models/PalmTree/StylizedWater.png new file mode 100644 index 0000000000..a4da3043ca Binary files /dev/null and b/assets/models/PalmTree/StylizedWater.png differ diff --git a/assets/shaders/custom_ui_material.wgsl b/assets/shaders/custom_ui_material.wgsl index fa0344930d..2b7eb01e92 100644 --- a/assets/shaders/custom_ui_material.wgsl +++ b/assets/shaders/custom_ui_material.wgsl @@ -5,10 +5,21 @@ @group(1) @binding(1) var slider: f32; @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; @fragment fn fragment(in: UiVertexOutput) -> @location(0) vec4 { + let r = in.uv - 0.5; + let b = vec2( + select(in.border_widths.x, in.border_widths.y, r.x < 0.), + select(in.border_widths.z, in.border_widths.w, r.y < 0.) + ); + + if any(0.5 - b < abs(r)) { + return border_color; + } + if in.uv.x < slider { let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color; return output_color; diff --git a/benches/benches/bevy_ecs/components/add_remove_very_big_table.rs b/benches/benches/bevy_ecs/components/add_remove_very_big_table.rs new file mode 100644 index 0000000000..1a4f238cd3 --- /dev/null +++ b/benches/benches/bevy_ecs/components/add_remove_very_big_table.rs @@ -0,0 +1,111 @@ +#![allow(dead_code)] + +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct A(Mat4); +#[derive(Component, Copy, Clone)] +struct B(Mat4); +#[derive(Component, Copy, Clone)] +struct C(Mat4); +#[derive(Component, Copy, Clone)] +struct D(Mat4); +#[derive(Component, Copy, Clone)] +struct E(Mat4); +#[derive(Component, Copy, Clone)] +struct F(Mat4); +#[derive(Component, Copy, Clone)] +struct Z; + +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + let mut entities = Vec::with_capacity(10_000); + for _ in 0..10_000 { + entities.push( + world + .spawn(( + ( + A::<1>(Mat4::from_scale(Vec3::ONE)), + B::<1>(Mat4::from_scale(Vec3::ONE)), + C::<1>(Mat4::from_scale(Vec3::ONE)), + D::<1>(Mat4::from_scale(Vec3::ONE)), + E::<1>(Mat4::from_scale(Vec3::ONE)), + A::<2>(Mat4::from_scale(Vec3::ONE)), + B::<2>(Mat4::from_scale(Vec3::ONE)), + C::<2>(Mat4::from_scale(Vec3::ONE)), + D::<2>(Mat4::from_scale(Vec3::ONE)), + E::<2>(Mat4::from_scale(Vec3::ONE)), + ), + ( + A::<3>(Mat4::from_scale(Vec3::ONE)), + B::<3>(Mat4::from_scale(Vec3::ONE)), + C::<3>(Mat4::from_scale(Vec3::ONE)), + D::<3>(Mat4::from_scale(Vec3::ONE)), + E::<3>(Mat4::from_scale(Vec3::ONE)), + A::<4>(Mat4::from_scale(Vec3::ONE)), + B::<4>(Mat4::from_scale(Vec3::ONE)), + C::<4>(Mat4::from_scale(Vec3::ONE)), + D::<4>(Mat4::from_scale(Vec3::ONE)), + E::<4>(Mat4::from_scale(Vec3::ONE)), + ), + ( + A::<5>(Mat4::from_scale(Vec3::ONE)), + B::<5>(Mat4::from_scale(Vec3::ONE)), + C::<5>(Mat4::from_scale(Vec3::ONE)), + D::<5>(Mat4::from_scale(Vec3::ONE)), + E::<5>(Mat4::from_scale(Vec3::ONE)), + A::<6>(Mat4::from_scale(Vec3::ONE)), + B::<6>(Mat4::from_scale(Vec3::ONE)), + C::<6>(Mat4::from_scale(Vec3::ONE)), + D::<6>(Mat4::from_scale(Vec3::ONE)), + E::<6>(Mat4::from_scale(Vec3::ONE)), + ), + ( + A::<7>(Mat4::from_scale(Vec3::ONE)), + B::<7>(Mat4::from_scale(Vec3::ONE)), + C::<7>(Mat4::from_scale(Vec3::ONE)), + D::<7>(Mat4::from_scale(Vec3::ONE)), + E::<7>(Mat4::from_scale(Vec3::ONE)), + Z::<1>, + Z::<2>, + Z::<3>, + Z::<4>, + Z::<5>, + Z::<6>, + Z::<7>, + ), + )) + .id(), + ); + } + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0.entity_mut(*entity).insert(( + F::<1>(Mat4::from_scale(Vec3::ONE)), + F::<2>(Mat4::from_scale(Vec3::ONE)), + F::<3>(Mat4::from_scale(Vec3::ONE)), + F::<4>(Mat4::from_scale(Vec3::ONE)), + F::<5>(Mat4::from_scale(Vec3::ONE)), + F::<6>(Mat4::from_scale(Vec3::ONE)), + F::<7>(Mat4::from_scale(Vec3::ONE)), + )); + } + + for entity in &self.1 { + self.0 + .entity_mut(*entity) + .remove::<(F<1>, F<2>, F<3>, F<4>, F<5>, F<6>, F<7>)>(); + self.0 + .entity_mut(*entity) + .remove::<(Z<1>, Z<2>, Z<3>, Z<4>, Z<5>, Z<6>, Z<7>)>(); + } + } +} diff --git a/benches/benches/bevy_ecs/components/mod.rs b/benches/benches/bevy_ecs/components/mod.rs index 419f938c91..6da9368100 100644 --- a/benches/benches/bevy_ecs/components/mod.rs +++ b/benches/benches/bevy_ecs/components/mod.rs @@ -4,6 +4,7 @@ mod add_remove_big_sparse_set; mod add_remove_big_table; mod add_remove_sparse_set; mod add_remove_table; +mod add_remove_very_big_table; mod archetype_updates; mod insert_simple; mod insert_simple_unbatched; @@ -14,6 +15,7 @@ criterion_group!( components_benches, add_remove, add_remove_big, + add_remove_very_big, insert_simple, no_archetypes, added_archetypes, @@ -49,6 +51,17 @@ fn add_remove_big(c: &mut Criterion) { group.finish(); } +fn add_remove_very_big(c: &mut Criterion) { + let mut group = c.benchmark_group("add_remove_very_big"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("table", |b| { + let mut bench = add_remove_very_big_table::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + fn insert_simple(c: &mut Criterion) { let mut group = c.benchmark_group("insert_simple"); group.warm_up_time(std::time::Duration::from_millis(500)); diff --git a/benches/benches/bevy_ecs/world/commands.rs b/benches/benches/bevy_ecs/world/commands.rs index d6b6c1438a..6994d9b10f 100644 --- a/benches/benches/bevy_ecs/world/commands.rs +++ b/benches/benches/bevy_ecs/world/commands.rs @@ -1,5 +1,3 @@ -use std::mem::size_of; - use bevy_ecs::{ component::Component, entity::Entity, @@ -146,9 +144,9 @@ pub fn fake_commands(criterion: &mut Criterion) { let mut commands = Commands::new(&mut command_queue, &world); for i in 0..command_count { if black_box(i % 2 == 0) { - commands.add(FakeCommandA); + commands.queue(FakeCommandA); } else { - commands.add(FakeCommandB(0)); + commands.queue(FakeCommandB(0)); } } command_queue.apply(&mut world); @@ -190,7 +188,7 @@ pub fn sized_commands_impl(criterion: &mut Criterion) { bencher.iter(|| { let mut commands = Commands::new(&mut command_queue, &world); for _ in 0..command_count { - commands.add(T::default()); + commands.queue(T::default()); } command_queue.apply(&mut world); }); diff --git a/clippy.toml b/clippy.toml index ccf511898b..d1d234817a 100644 --- a/clippy.toml +++ b/clippy.toml @@ -10,3 +10,35 @@ doc-valid-idents = [ "WebGPU", "..", ] + +check-private-items = true + +disallowed-methods = [ + { path = "f32::powi", reason = "use bevy_math::ops::FloatPow::squared, bevy_math::ops::FloatPow::cubed, or bevy_math::ops::powf instead for libm determinism" }, + { path = "f32::log", reason = "use bevy_math::ops::ln, bevy_math::ops::log2, or bevy_math::ops::log10 instead for libm determinism" }, + { path = "f32::abs_sub", reason = "deprecated and deeply confusing method" }, + { path = "f32::powf", reason = "use bevy_math::ops::powf instead for libm determinism" }, + { path = "f32::exp", reason = "use bevy_math::ops::exp instead for libm determinism" }, + { path = "f32::exp2", reason = "use bevy_math::ops::exp2 instead for libm determinism" }, + { path = "f32::ln", reason = "use bevy_math::ops::ln instead for libm determinism" }, + { path = "f32::log2", reason = "use bevy_math::ops::log2 instead for libm determinism" }, + { path = "f32::log10", reason = "use bevy_math::ops::log10 instead for libm determinism" }, + { path = "f32::cbrt", reason = "use bevy_math::ops::cbrt instead for libm determinism" }, + { path = "f32::hypot", reason = "use bevy_math::ops::hypot instead for libm determinism" }, + { path = "f32::sin", reason = "use bevy_math::ops::sin instead for libm determinism" }, + { path = "f32::cos", reason = "use bevy_math::ops::cos instead for libm determinism" }, + { path = "f32::tan", reason = "use bevy_math::ops::tan instead for libm determinism" }, + { path = "f32::asin", reason = "use bevy_math::ops::asin instead for libm determinism" }, + { path = "f32::acos", reason = "use bevy_math::ops::acos instead for libm determinism" }, + { path = "f32::atan", reason = "use bevy_math::ops::atan instead for libm determinism" }, + { path = "f32::atan2", reason = "use bevy_math::ops::atan2 instead for libm determinism" }, + { path = "f32::sin_cos", reason = "use bevy_math::ops::sin_cos instead for libm determinism" }, + { path = "f32::exp_m1", reason = "use bevy_math::ops::exp_m1 instead for libm determinism" }, + { path = "f32::ln_1p", reason = "use bevy_math::ops::ln_1p instead for libm determinism" }, + { path = "f32::sinh", reason = "use bevy_math::ops::sinh instead for libm determinism" }, + { path = "f32::cosh", reason = "use bevy_math::ops::cosh instead for libm determinism" }, + { path = "f32::tanh", reason = "use bevy_math::ops::tanh instead for libm determinism" }, + { path = "f32::asinh", reason = "use bevy_math::ops::asinh instead for libm determinism" }, + { path = "f32::acosh", reason = "use bevy_math::ops::acosh instead for libm determinism" }, + { path = "f32::atanh", reason = "use bevy_math::ops::atanh instead for libm determinism" }, +] diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs index 92f514ab45..66b5d333be 100644 --- a/crates/bevy_a11y/src/lib.rs +++ b/crates/bevy_a11y/src/lib.rs @@ -21,6 +21,7 @@ use bevy_ecs::{ schedule::SystemSet, system::Resource, }; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; /// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`. @@ -94,7 +95,7 @@ impl From for AccessibilityNode { /// Resource representing which entity has keyboard focus, if any. #[derive(Resource, Default, Deref, DerefMut, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Default)] pub struct Focus(pub Option); /// Set enum for the systems relating to accessibility diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 7be1b6d88e..c8f521fdbc 100755 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -22,7 +22,8 @@ use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; use bevy_core::Name; use bevy_ecs::{entity::MapEntities, prelude::*, reflect::ReflectMapEntities}; -use bevy_math::{FloatExt, Quat, Vec3}; +use bevy_math::{FloatExt, FloatPow, Quat, Vec3}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::mesh::morph::MorphWeights; use bevy_time::Time; @@ -540,7 +541,7 @@ impl ActiveAnimation { /// Automatically added to any root animations of a `SceneBundle` when it is /// spawned. #[derive(Component, Default, Reflect)] -#[reflect(Component)] +#[reflect(Component, Default)] pub struct AnimationPlayer { /// We use a `BTreeMap` instead of a `HashMap` here to ensure a consistent /// ordering when applying the animations. @@ -1210,10 +1211,10 @@ fn cubic_spline_interpolation( where T: Mul + Add, { - value_start * (2.0 * lerp.powi(3) - 3.0 * lerp.powi(2) + 1.0) - + tangent_out_start * (step_duration) * (lerp.powi(3) - 2.0 * lerp.powi(2) + lerp) - + value_end * (-2.0 * lerp.powi(3) + 3.0 * lerp.powi(2)) - + tangent_in_end * step_duration * (lerp.powi(3) - lerp.powi(2)) + value_start * (2.0 * lerp.cubed() - 3.0 * lerp.squared() + 1.0) + + tangent_out_start * (step_duration) * (lerp.cubed() - 2.0 * lerp.squared() + lerp) + + value_end * (-2.0 * lerp.cubed() + 3.0 * lerp.squared()) + + tangent_in_end * step_duration * (lerp.cubed() - lerp.squared()) } /// Adds animation support to an app diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index da41efbc15..a6a0243ee3 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1115,7 +1115,7 @@ impl Termination for AppExit { #[cfg(test)] mod tests { - use std::{iter, marker::PhantomData, mem::size_of, sync::Mutex}; + use std::{iter, marker::PhantomData, sync::Mutex}; use bevy_ecs::{ change_detection::{DetectChanges, ResMut}, diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 95e480493a..5f78dc76c6 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; -use bevy_utils::get_short_name; +use bevy_utils::ShortName; use crossbeam_channel::{Receiver, Sender}; use std::{ any::TypeId, @@ -206,7 +206,7 @@ impl Default for Handle { impl std::fmt::Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = get_short_name(std::any::type_name::()); + let name = ShortName::of::(); match self { Handle::Strong(handle) => { write!( diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index 5ec179f846..92f99af42e 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -51,8 +51,7 @@ impl FileAssetReader { /// Returns the base path of the assets directory, which is normally the executable's parent /// directory. /// - /// If the `CARGO_MANIFEST_DIR` environment variable is set, then its value will be used - /// instead. It's set by cargo when running with `cargo run`. + /// To change this, set [`AssetPlugin.file_path`]. pub fn get_base_path() -> PathBuf { get_base_path() } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index dbe559d5f2..925192e6de 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -26,7 +26,6 @@ use futures_io::{AsyncRead, AsyncSeek, AsyncWrite}; use futures_lite::{ready, Stream}; use std::{ io::SeekFrom, - mem::size_of, path::{Path, PathBuf}, pin::Pin, sync::Arc, @@ -130,7 +129,10 @@ where /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given /// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead. /// -/// Also see [`AssetWriter`]. +/// This trait defines asset-agnostic mechanisms to read bytes from a storage system. +/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader). +/// +/// For a complementary version of this trait that can write assets to storage, see [`AssetWriter`]. pub trait AssetReader: Send + Sync + 'static { /// Returns a future to load the full file data at the provided path. /// @@ -261,7 +263,10 @@ pub enum AssetWriterError { /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given /// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead. /// -/// Also see [`AssetReader`]. +/// This trait defines asset-agnostic mechanisms to write bytes to a storage system. +/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader). +/// +/// For a complementary version of this trait that can read assets from storage, see [`AssetReader`]. pub trait AssetWriter: Send + Sync + 'static { /// Writes the full asset bytes at the provided path. fn write<'a>( diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index f7787fb809..26092d976f 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1,3 +1,143 @@ +//! In the context of game development, an "asset" is a piece of content that is loaded from disk and displayed in the game. +//! Typically, these are authored by artists and designers (in contrast to code), +//! are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts. +//! +//! This presents two main challenges: +//! - Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive. +//! - Loading assets from disk is slow, and can cause long load times and delays. +//! +//! These problems play into each other, for if assets are expensive to store in memory, +//! then larger game worlds will need to load them from disk as needed, ideally without a loading screen. +//! +//! As is common in Rust, non-blocking asset loading is done using `async`, with background tasks used to load assets while the game is running. +//! Bevy coordinates these tasks using the [`AssetServer`] resource, storing each loaded asset in a strongly-typed [`Assets`] collection (also a resource). +//! [`Handle`]s serve as an id-based reference to entries in the [`Assets`] collection, allowing them to be cheaply shared between systems, +//! and providing a way to initialize objects (generally entities) before the required assets are loaded. +//! In short: [`Handle`]s are not the assets themselves, they just tell how to look them up! +//! +//! ## Loading assets +//! +//! The [`AssetServer`] is the main entry point for loading assets. +//! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`]. +//! Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped, +//! calling [`AssetServer::load`] on the same path will return the same handle. +//! The handle that's returned can be used to instantiate various [`Component`](bevy_ecs::prelude::Component)s that require asset data to function, +//! which will then be spawned into the world as part of an entity. +//! +//! To avoid assets "popping" into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene. +//! This can be done by checking the [`LoadState`] of the asset handle using [`AssetServer::is_loaded_with_dependencies`], +//! which will be `true` when the asset is ready to use. +//! +//! Keep track of what you're waiting on by using a [`HashSet`] of asset handles or similar data structure, +//! which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded. +//! Bevy's built-in states system can be very helpful for this! +//! +//! # Modifying entities that use assets +//! +//! If we later want to change the asset data a given component uses (such as changing an entity's material), we have three options: +//! +//! 1. Change the handle stored on the responsible component to the handle of a different asset +//! 2. Despawn the entity and spawn a new one with the new asset data. +//! 3. Use the [`Assets`] collection to directly modify the current handle's asset data +//! +//! The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset. +//! Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset, +//! you'll need to find that component via a query and change the handle to the new mesh asset. +//! This is so commonly done that you should think about strategies for how to store and swap handles in your game. +//! +//! The second option is the simplest, but can be slow if done frequently, +//! and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost. +//! Generally, this isn't a great strategy. +//! +//! The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for *all* entities using this handle. +//! While this might be what you want, it generally isn't! +//! +//! # Hot reloading assets +//! +//! Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting. +//! When enabled, any changes to the underlying asset file will be detected by the [`AssetServer`], which will then reload the asset, +//! mutating the asset data in the [`Assets`] collection and thus updating all entities that use the asset. +//! While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly. +//! +//! To enable asset hot reloading on desktop platforms, enable `bevy`'s `file_watcher` cargo feature. +//! To toggle it at runtime, you can use the `watch_for_changes_override` field in the [`AssetPlugin`] to enable or disable hot reloading. +//! +//! # Procedural asset creation +//! +//! Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels. +//! After creating an item of a type that implements [`Asset`], you can add it to the [`Assets`] collection using [`Assets::add`]. +//! Once in the asset collection, this data can be operated on like any other asset. +//! +//! Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets: +//! calling [`Assets::add`] for every entity that needs the asset will create a new copy of the asset for each entity, +//! quickly consuming memory. +//! +//! ## Handles and reference counting +//! +//! [`Handle`] (or their untyped counterpart [`UntypedHandle`]) are used to reference assets in the [`Assets`] collection, +//! and are the primary way to interact with assets in Bevy. +//! As a user, you'll be working with handles a lot! +//! +//! The most important thing to know about handles is that they are reference counted: when you clone a handle, you're incrementing a reference count. +//! When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented. +//! When the reference count hits zero, the asset it references is removed from the [`Assets`] collection. +//! +//! This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use. +//! However, it can lead to surprising behavior if you're not careful! +//! +//! There are two categories of problems to watch out for: +//! - never dropping a handle, causing the asset to never be removed from memory +//! - dropping a handle too early, causing the asset to be removed from memory while it's still in use +//! +//! The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once, +//! and loading them all at the start of the game. +//! As your game grows, you'll need to be more careful about when you load and unload assets, +//! segmenting them by level or area, and loading them on-demand. +//! This problem generally arises when handles are stored in a persistent "collection" or "manifest" of possible objects (generally in a resource), +//! which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage. +//! +//! The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game. +//! Debugging reveals that the *entities* are still there, but nothing is rendering! +//! This is because the assets were removed from memory while they were still in use. +//! You were probably too aggressive with the use of weak handles (which don't increment the reference count of the asset): think through the lifecycle of your assets carefully! +//! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player. +//! +//! # Asset dependencies +//! +//! Some assets depend on other assets to be loaded before they can be loaded themselves. +//! For example, a 3D model might require both textures and meshes to be loaded, +//! or a 2D level might require a tileset to be loaded. +//! +//! The assets that are required to load another asset are called "dependencies". +//! An asset is only considered fully loaded when it and all of its dependencies are loaded. +//! Asset dependencies can be declared when implementing the [`Asset`] trait by implementing the [`VisitAssetDependencies`] trait, +//! and the `#[dependency]` attribute can be used to automatically derive this implementation. +//! +//! # Custom asset types +//! +//! While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!), +//! implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats. +//! +//! Defining a new asset type is as simple as implementing the [`Asset`] trait. +//! This requires [`TypePath`] for metadata about the asset type, +//! and [`VisitAssetDependencies`] to track asset dependencies. +//! In simple cases, you can derive [`Asset`] and [`Reflect`] and be done with it: the required supertraits will be implemented for you. +//! +//! With a new asset type in place, we now need to figure out how to load it. +//! While [`AssetReader`](io::AssetReader) describes strategies to read asset bytes from various sources, +//! [`AssetLoader`] is the trait that actually turns those into your desired in-memory format. +//! Generally, (only) [`AssetLoader`] needs to be implemented for custom assets, as the [`AssetReader`](io::AssetReader) implementations are provided by Bevy. +//! +//! However, [`AssetLoader`] shouldn't be implemented for your asset type directly: instead, this is implemented for a "loader" type +//! that can store settings and any additional data required to load your asset, while your asset type is used as the [`AssetLoader::Asset`] associated type. +//! As the trait documentation explains, this allows various [`AssetLoader::Settings`] to be used to configure the loader. +//! +//! After the loader is implemented, it needs to be registered with the [`AssetServer`] using [`App::register_asset_loader`](AssetApp::register_asset_loader). +//! Once your asset type is loaded, you can use it in your game like any other asset type! +//! +//! If you want to save your assets back to disk, you should implement [`AssetSaver`](saver::AssetSaver) as well. +//! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader). + // FIXME(3492): remove once docs are ready #![allow(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -100,6 +240,12 @@ pub struct AssetPlugin { pub meta_check: AssetMetaCheck, } +/// Controls whether or not assets are pre-processed before being loaded. +/// +/// This setting is controlled by setting [`AssetPlugin::mode`]. +/// +/// When building on web, asset preprocessing can cause problems due to the lack of filesystem access. +/// See [bevy#10157](https://github.com/bevyengine/bevy/issues/10157) for context. #[derive(Debug)] pub enum AssetMode { /// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing". @@ -234,6 +380,13 @@ impl Plugin for AssetPlugin { } } +/// Declares that this type is an asset, +/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections. +/// +/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers. +/// +/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type. +/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`]. #[diagnostic::on_unimplemented( message = "`{Self}` is not an `Asset`", label = "invalid `Asset`", @@ -241,6 +394,10 @@ impl Plugin for AssetPlugin { )] pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {} +/// This trait defines how to visit the dependencies of an asset. +/// For example, a 3D model might require both textures and meshes to be loaded. +/// +/// Note that this trait is automatically implemented when deriving [`Asset`]. pub trait VisitAssetDependencies { fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)); } @@ -1089,13 +1246,19 @@ mod tests { assert!(d_text.is_none()); assert!(matches!(d_load, LoadState::Failed(_))); - assert_eq!(d_deps, DependencyLoadState::Failed); - assert_eq!(d_rec_deps, RecursiveDependencyLoadState::Failed); + assert!(matches!(d_deps, DependencyLoadState::Failed(_))); + assert!(matches!( + d_rec_deps, + RecursiveDependencyLoadState::Failed(_) + )); assert_eq!(a_text.text, "a"); assert_eq!(a_load, LoadState::Loaded); assert_eq!(a_deps, DependencyLoadState::Loaded); - assert_eq!(a_rec_deps, RecursiveDependencyLoadState::Failed); + assert!(matches!( + a_rec_deps, + RecursiveDependencyLoadState::Failed(_) + )); assert_eq!(b_text.text, "b"); assert_eq!(b_load, LoadState::Loaded); @@ -1104,9 +1267,127 @@ mod tests { assert_eq!(c_text.text, "c"); assert_eq!(c_load, LoadState::Loaded); - assert_eq!(c_deps, DependencyLoadState::Failed); - assert_eq!(c_rec_deps, RecursiveDependencyLoadState::Failed); + assert!(matches!(c_deps, DependencyLoadState::Failed(_))); + assert!(matches!( + c_rec_deps, + RecursiveDependencyLoadState::Failed(_) + )); + assert_eq!(asset_server.load_state(a_id), LoadState::Loaded); + assert_eq!( + asset_server.dependency_load_state(a_id), + DependencyLoadState::Loaded + ); + assert!(matches!( + asset_server.recursive_dependency_load_state(a_id), + RecursiveDependencyLoadState::Failed(_) + )); + + assert!(asset_server.is_loaded(a_id)); + assert!(asset_server.is_loaded_with_direct_dependencies(a_id)); + assert!(!asset_server.is_loaded_with_dependencies(a_id)); + + Some(()) + }); + } + + #[test] + fn dependency_load_states() { + // The particular usage of GatedReader in this test will cause deadlocking if running single-threaded + #[cfg(not(feature = "multi_threaded"))] + panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded"); + + let a_path = "a.cool.ron"; + let a_ron = r#" +( + text: "a", + dependencies: [ + "b.cool.ron", + "c.cool.ron", + ], + embedded_dependencies: [], + sub_texts: [] +)"#; + let b_path = "b.cool.ron"; + let b_ron = r#" +( + text: "b", + dependencies: [], + MALFORMED + embedded_dependencies: [], + sub_texts: [] +)"#; + + let c_path = "c.cool.ron"; + let c_ron = r#" +( + text: "c", + dependencies: [], + embedded_dependencies: [], + sub_texts: [] +)"#; + + let dir = Dir::default(); + dir.insert_asset_text(Path::new(a_path), a_ron); + dir.insert_asset_text(Path::new(b_path), b_ron); + dir.insert_asset_text(Path::new(c_path), c_ron); + + let (mut app, gate_opener) = test_app(dir); + app.init_asset::() + .register_asset_loader(CoolTextLoader); + let asset_server = app.world().resource::().clone(); + let handle: Handle = asset_server.load(a_path); + let a_id = handle.id(); + app.world_mut().spawn(handle); + + gate_opener.open(a_path); + run_app_until(&mut app, |world| { + let _a_text = get::(world, a_id)?; + let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap(); + assert_eq!(a_load, LoadState::Loaded); + assert_eq!(a_deps, DependencyLoadState::Loading); + assert_eq!(a_rec_deps, RecursiveDependencyLoadState::Loading); + Some(()) + }); + + gate_opener.open(b_path); + run_app_until(&mut app, |world| { + let a_text = get::(world, a_id)?; + let b_id = a_text.dependencies[0].id(); + + let (b_load, _b_deps, _b_rec_deps) = asset_server.get_load_states(b_id).unwrap(); + if !matches!(b_load, LoadState::Failed(_)) { + // wait until b fails + return None; + } + + let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap(); + assert_eq!(a_load, LoadState::Loaded); + assert!(matches!(a_deps, DependencyLoadState::Failed(_))); + assert!(matches!( + a_rec_deps, + RecursiveDependencyLoadState::Failed(_) + )); + Some(()) + }); + + gate_opener.open(c_path); + run_app_until(&mut app, |world| { + let a_text = get::(world, a_id)?; + let c_id = a_text.dependencies[1].id(); + // wait until c loads + let _c_text = get::(world, c_id)?; + + let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap(); + assert_eq!(a_load, LoadState::Loaded); + assert!( + matches!(a_deps, DependencyLoadState::Failed(_)), + "Successful dependency load should not overwrite a previous failure" + ); + assert!( + matches!(a_rec_deps, RecursiveDependencyLoadState::Failed(_)), + "Successful dependency load should not overwrite a previous failure" + ); Some(()) }); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index f0dce3593d..f8521b4cda 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -20,6 +20,10 @@ use thiserror::Error; /// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`] /// should be loaded. +/// +/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source. +/// +/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver). pub trait AssetLoader: Send + Sync + 'static { /// The top level [`Asset`] loaded by this [`AssetLoader`]. type Asset: Asset; diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 15acdbdc8d..31ddb33b77 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -1,3 +1,42 @@ +//! Asset processing in Bevy is a framework for automatically transforming artist-authored assets into the format that best suits the needs of your particular game. +//! +//! You can think of the asset processing system as a "build system" for assets. +//! When an artist adds a new asset to the project or an asset is changed (assuming asset hot reloading is enabled), the asset processing system will automatically perform the specified processing steps on the asset. +//! This can include things like creating lightmaps for baked lighting, compressing a `.wav` file to an `.ogg`, or generating mipmaps for a texture. +//! +//! Its core values are: +//! +//! 1. Automatic: new and changed assets should be ready to use in-game without requiring any manual conversion or cleanup steps. +//! 2. Configurable: every game has its own needs, and a high level of transparency and control is required. +//! 3. Lossless: the original asset should always be preserved, ensuring artists can make changes later. +//! 4. Deterministic: performing the same processing steps on the same asset should (generally) produce the exact same result. In cases where this doesn't make sense (steps that involve a degree of randomness or uncertainty), the results across runs should be "acceptably similar", as they will be generated once for a given set of inputs and cached. +//! +//! Taken together, this means that the original asset plus the processing steps should be enough to regenerate the final asset. +//! While it may be possible to manually edit the final asset, this should be discouraged. +//! Final post-processed assets should generally not be version-controlled, except to save developer time when recomputing heavy asset processing steps. +//! +//! # Usage +//! +//! Asset processing can be enabled or disabled in [`AssetPlugin`](crate::AssetPlugin) by setting the [`AssetMode`](crate::AssetMode).\ +//! Enable Bevy's `file_watcher` feature to automatically watch for changes to assets and reprocess them. +//! +//! To register a new asset processor, use [`AssetProcessor::register_processor`]. +//! To set the default asset processor for a given extension, use [`AssetProcessor::set_default_processor`]. +//! In most cases, these methods will be called directly on [`App`](bevy_app::App) using the [`AssetApp`](crate::AssetApp) extension trait. +//! +//! If a default asset processor is set, assets with a matching extension will be processed using that processor before loading. +//! +//! For an end-to-end example, check out the examples in the [`examples/asset/processing`](https://github.com/bevyengine/bevy/tree/latest/examples/asset/processing) directory of the Bevy repository. +//! +//! # Defining asset processors +//! +//! Bevy provides two different ways to define new asset processors: +//! +//! - [`LoadTransformAndSave`] + [`AssetTransformer`](crate::transformer::AssetTransformer): a high-level API for loading, transforming, and saving assets. +//! - [`Process`]: a flexible low-level API for processing assets in arbitrary ways. +//! +//! In most cases, [`LoadTransformAndSave`] should be sufficient. + mod log; mod process; @@ -61,6 +100,7 @@ pub struct AssetProcessor { pub(crate) data: Arc, } +/// Internal data stored inside an [`AssetProcessor`]. pub struct AssetProcessorData { pub(crate) asset_infos: async_lock::RwLock, log: async_lock::RwLock>, @@ -91,6 +131,7 @@ impl AssetProcessor { Self { server, data } } + /// Gets a reference to the [`Arc`] containing the [`AssetProcessorData`]. pub fn data(&self) -> &Arc { &self.data } @@ -965,6 +1006,7 @@ impl AssetProcessor { } impl AssetProcessorData { + /// Initializes a new [`AssetProcessorData`] using the given [`AssetSources`]. pub fn new(source: AssetSources) -> Self { let (mut finished_sender, finished_receiver) = async_broadcast::broadcast(1); let (mut initialized_sender, initialized_receiver) = async_broadcast::broadcast(1); diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index 349c047005..92a13f21ff 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -1,4 +1,5 @@ use crate::io::SliceReader; +use crate::transformer::IdentityAssetTransformer; use crate::{ io::{ AssetReaderError, AssetWriterError, MissingAssetWriterError, @@ -47,6 +48,11 @@ pub trait Process: Send + Sync + Sized + 'static { /// an [`AssetSaver`] that allows you save any `S` asset. However you can /// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary. /// +/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`. +/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`]. +/// However, this pattern should only be used for cases such as file format conversion. +/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`]. +/// /// This uses [`LoadTransformAndSaveSettings`] to configure the processor. /// /// [`Asset`]: crate::Asset @@ -60,6 +66,18 @@ pub struct LoadTransformAndSave< marker: PhantomData L>, } +impl> From + for LoadTransformAndSave, S> +{ + fn from(value: S) -> Self { + LoadTransformAndSave { + transformer: IdentityAssetTransformer::new(), + saver: value, + marker: PhantomData, + } + } +} + /// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation. /// /// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`], @@ -98,30 +116,16 @@ impl< /// This uses [`LoadAndSaveSettings`] to configure the processor. /// /// [`Asset`]: crate::Asset -pub struct LoadAndSave> { - saver: S, - marker: PhantomData L>, -} - -impl> From for LoadAndSave { - fn from(value: S) -> Self { - LoadAndSave { - saver: value, - marker: PhantomData, - } - } -} +#[deprecated = "Use `LoadTransformAndSave::Asset>, S>` instead"] +pub type LoadAndSave = + LoadTransformAndSave::Asset>, S>; /// Settings for the [`LoadAndSave`] [`Process::Settings`] implementation. /// /// `LoaderSettings` corresponds to [`AssetLoader::Settings`] and `SaverSettings` corresponds to [`AssetSaver::Settings`]. -#[derive(Serialize, Deserialize, Default)] -pub struct LoadAndSaveSettings { - /// The [`AssetLoader::Settings`] for [`LoadAndSave`]. - pub loader_settings: LoaderSettings, - /// The [`AssetSaver::Settings`] for [`LoadAndSave`]. - pub saver_settings: SaverSettings, -} +#[deprecated = "Use `LoadTransformAndSaveSettings` instead"] +pub type LoadAndSaveSettings = + LoadTransformAndSaveSettings; /// An error that is encountered during [`Process::process`]. #[derive(Error, Debug)] @@ -213,36 +217,6 @@ where } } -impl> Process - for LoadAndSave -{ - type Settings = LoadAndSaveSettings; - type OutputLoader = Saver::OutputLoader; - - async fn process<'a>( - &'a self, - context: &'a mut ProcessContext<'_>, - meta: AssetMeta<(), Self>, - writer: &'a mut Writer, - ) -> Result<::Settings, ProcessError> { - let AssetAction::Process { settings, .. } = meta.asset else { - return Err(ProcessError::WrongMetaType); - }; - let loader_meta = AssetMeta::::new(AssetAction::Load { - loader: std::any::type_name::().to_string(), - settings: settings.loader_settings, - }); - let loaded_asset = context.load_source_asset(loader_meta).await?; - let saved_asset = SavedAsset::::from_loaded(&loaded_asset).unwrap(); - let output_settings = self - .saver - .save(writer, saved_asset, &settings.saver_settings) - .await - .map_err(|error| ProcessError::AssetSaveError(error.into()))?; - Ok(output_settings) - } -} - /// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing /// their type. pub trait ErasedProcessor: Send + Sync { diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 4d5925dc54..115a880377 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -8,6 +8,10 @@ use std::{borrow::Borrow, hash::Hash, ops::Deref}; /// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset /// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read. +/// +/// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes. +/// +/// For a complementary version of this trait that can load assets, see [`AssetLoader`]. pub trait AssetSaver: Send + Sync + 'static { /// The top level [`Asset`] saved by this [`AssetSaver`]. type Asset: Asset; diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index b63c139f32..82bc285f21 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -388,8 +388,10 @@ impl AssetInfos { loaded_asset.value.insert(loaded_asset_id, world); let mut loading_deps = loaded_asset.dependencies; let mut failed_deps = HashSet::new(); + let mut dep_error = None; let mut loading_rec_deps = loading_deps.clone(); let mut failed_rec_deps = HashSet::new(); + let mut rec_dep_error = None; loading_deps.retain(|dep_id| { if let Some(dep_info) = self.get_mut(*dep_id) { match dep_info.rec_dep_load_state { @@ -404,7 +406,10 @@ impl AssetInfos { // If dependency is loaded, reduce our count by one loading_rec_deps.remove(dep_id); } - RecursiveDependencyLoadState::Failed => { + RecursiveDependencyLoadState::Failed(ref error) => { + if rec_dep_error.is_none() { + rec_dep_error = Some(error.clone()); + } failed_rec_deps.insert(*dep_id); loading_rec_deps.remove(dep_id); } @@ -419,7 +424,10 @@ impl AssetInfos { // If dependency is loaded, reduce our count by one false } - LoadState::Failed(_) => { + LoadState::Failed(ref error) => { + if dep_error.is_none() { + dep_error = Some(error.clone()); + } failed_deps.insert(*dep_id); false } @@ -437,7 +445,7 @@ impl AssetInfos { let dep_load_state = match (loading_deps.len(), failed_deps.len()) { (0, 0) => DependencyLoadState::Loaded, (_loading, 0) => DependencyLoadState::Loading, - (_loading, _failed) => DependencyLoadState::Failed, + (_loading, _failed) => DependencyLoadState::Failed(dep_error.unwrap()), }; let rec_dep_load_state = match (loading_rec_deps.len(), failed_rec_deps.len()) { @@ -450,7 +458,7 @@ impl AssetInfos { RecursiveDependencyLoadState::Loaded } (_loading, 0) => RecursiveDependencyLoadState::Loading, - (_loading, _failed) => RecursiveDependencyLoadState::Failed, + (_loading, _failed) => RecursiveDependencyLoadState::Failed(rec_dep_error.unwrap()), }; let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = { @@ -480,14 +488,14 @@ impl AssetInfos { info.failed_rec_dependencies = failed_rec_deps; info.load_state = LoadState::Loaded; info.dep_load_state = dep_load_state; - info.rec_dep_load_state = rec_dep_load_state; + info.rec_dep_load_state = rec_dep_load_state.clone(); if watching_for_changes { info.loader_dependencies = loaded_asset.loader_dependencies; } let dependants_waiting_on_rec_load = if matches!( rec_dep_load_state, - RecursiveDependencyLoadState::Loaded | RecursiveDependencyLoadState::Failed + RecursiveDependencyLoadState::Loaded | RecursiveDependencyLoadState::Failed(_) ) { Some(std::mem::take( &mut info.dependants_waiting_on_recursive_dep_load, @@ -505,7 +513,9 @@ impl AssetInfos { for id in dependants_waiting_on_load { if let Some(info) = self.get_mut(id) { info.loading_dependencies.remove(&loaded_asset_id); - if info.loading_dependencies.is_empty() { + if info.loading_dependencies.is_empty() + && !matches!(info.dep_load_state, DependencyLoadState::Failed(_)) + { // send dependencies loaded event info.dep_load_state = DependencyLoadState::Loaded; } @@ -519,9 +529,9 @@ impl AssetInfos { Self::propagate_loaded_state(self, loaded_asset_id, dep_id, sender); } } - RecursiveDependencyLoadState::Failed => { + RecursiveDependencyLoadState::Failed(ref error) => { for dep_id in dependants_waiting_on_rec_load { - Self::propagate_failed_state(self, loaded_asset_id, dep_id); + Self::propagate_failed_state(self, loaded_asset_id, dep_id, error); } } RecursiveDependencyLoadState::Loading | RecursiveDependencyLoadState::NotLoaded => { @@ -570,11 +580,12 @@ impl AssetInfos { infos: &mut AssetInfos, failed_id: UntypedAssetId, waiting_id: UntypedAssetId, + error: &Arc, ) { let dependants_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) { info.loading_rec_dependencies.remove(&failed_id); info.failed_rec_dependencies.insert(failed_id); - info.rec_dep_load_state = RecursiveDependencyLoadState::Failed; + info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone()); Some(std::mem::take( &mut info.dependants_waiting_on_recursive_dep_load, )) @@ -584,7 +595,7 @@ impl AssetInfos { if let Some(dependants_waiting_on_rec_load) = dependants_waiting_on_rec_load { for dep_id in dependants_waiting_on_rec_load { - Self::propagate_failed_state(infos, waiting_id, dep_id); + Self::propagate_failed_state(infos, waiting_id, dep_id, error); } } } @@ -595,14 +606,15 @@ impl AssetInfos { return; } + let error = Arc::new(error); let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = { let Some(info) = self.get_mut(failed_id) else { // The asset was already dropped. return; }; - info.load_state = LoadState::Failed(Box::new(error)); - info.dep_load_state = DependencyLoadState::Failed; - info.rec_dep_load_state = RecursiveDependencyLoadState::Failed; + info.load_state = LoadState::Failed(error.clone()); + info.dep_load_state = DependencyLoadState::Failed(error.clone()); + info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone()); ( std::mem::take(&mut info.dependants_waiting_on_load), std::mem::take(&mut info.dependants_waiting_on_recursive_dep_load), @@ -613,12 +625,15 @@ impl AssetInfos { if let Some(info) = self.get_mut(waiting_id) { info.loading_dependencies.remove(&failed_id); info.failed_dependencies.insert(failed_id); - info.dep_load_state = DependencyLoadState::Failed; + // don't overwrite DependencyLoadState if already failed to preserve first error + if !(matches!(info.dep_load_state, DependencyLoadState::Failed(_))) { + info.dep_load_state = DependencyLoadState::Failed(error.clone()); + } } } for waiting_id in dependants_waiting_on_rec_load { - Self::propagate_failed_state(self, failed_id, waiting_id); + Self::propagate_failed_state(self, failed_id, waiting_id, &error); } } diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index a9e5fcddbf..1053bd7f95 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -266,6 +266,9 @@ impl AssetServer { /// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the /// associated [`Assets`] resource. /// + /// Note that if the asset at this path is already loaded, this function will return the existing handle, + /// and will not waste work spawning a new load task. + /// /// In case the file path contains a hashtag (`#`), the `path` must be specified using [`Path`] /// or [`AssetPath`] because otherwise the hashtag would be interpreted as separator between /// the file path and the label. For example: @@ -895,17 +898,20 @@ impl AssetServer { &self, id: impl Into, ) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> { - self.data - .infos - .read() - .get(id.into()) - .map(|i| (i.load_state.clone(), i.dep_load_state, i.rec_dep_load_state)) + self.data.infos.read().get(id.into()).map(|i| { + ( + i.load_state.clone(), + i.dep_load_state.clone(), + i.rec_dep_load_state.clone(), + ) + }) } /// Retrieves the main [`LoadState`] of a given asset `id`. /// - /// Note that this is "just" the root asset load state. To check if an asset _and_ its recursive - /// dependencies have loaded, see [`AssetServer::is_loaded_with_dependencies`]. + /// Note that this is "just" the root asset load state. To get the load state of + /// its dependencies or recursive dependencies, see [`AssetServer::get_dependency_load_state`] + /// and [`AssetServer::get_recursive_dependency_load_state`] respectively. pub fn get_load_state(&self, id: impl Into) -> Option { self.data .infos @@ -914,7 +920,27 @@ impl AssetServer { .map(|i| i.load_state.clone()) } - /// Retrieves the [`RecursiveDependencyLoadState`] of a given asset `id`. + /// Retrieves the [`DependencyLoadState`] of a given asset `id`'s dependencies. + /// + /// Note that this is only the load state of direct dependencies of the root asset. To get + /// the load state of the root asset itself or its recursive dependencies, see + /// [`AssetServer::get_load_state`] and [`AssetServer::get_recursive_dependency_load_state`] respectively. + pub fn get_dependency_load_state( + &self, + id: impl Into, + ) -> Option { + self.data + .infos + .read() + .get(id.into()) + .map(|i| i.dep_load_state.clone()) + } + + /// Retrieves the main [`RecursiveDependencyLoadState`] of a given asset `id`'s recursive dependencies. + /// + /// Note that this is only the load state of recursive dependencies of the root asset. To get + /// the load state of the root asset itself or its direct dependencies only, see + /// [`AssetServer::get_load_state`] and [`AssetServer::get_dependency_load_state`] respectively. pub fn get_recursive_dependency_load_state( &self, id: impl Into, @@ -923,15 +949,30 @@ impl AssetServer { .infos .read() .get(id.into()) - .map(|i| i.rec_dep_load_state) + .map(|i| i.rec_dep_load_state.clone()) } /// Retrieves the main [`LoadState`] of a given asset `id`. + /// + /// This is the same as [`AssetServer::get_load_state`] except the result is unwrapped. If + /// the result is None, [`LoadState::NotLoaded`] is returned. pub fn load_state(&self, id: impl Into) -> LoadState { self.get_load_state(id).unwrap_or(LoadState::NotLoaded) } + /// Retrieves the [`DependencyLoadState`] of a given asset `id`. + /// + /// This is the same as [`AssetServer::get_dependency_load_state`] except the result is unwrapped. If + /// the result is None, [`DependencyLoadState::NotLoaded`] is returned. + pub fn dependency_load_state(&self, id: impl Into) -> DependencyLoadState { + self.get_dependency_load_state(id) + .unwrap_or(DependencyLoadState::NotLoaded) + } + /// Retrieves the [`RecursiveDependencyLoadState`] of a given asset `id`. + /// + /// This is the same as [`AssetServer::get_recursive_dependency_load_state`] except the result is unwrapped. If + /// the result is None, [`RecursiveDependencyLoadState::NotLoaded`] is returned. pub fn recursive_dependency_load_state( &self, id: impl Into, @@ -940,11 +981,30 @@ impl AssetServer { .unwrap_or(RecursiveDependencyLoadState::NotLoaded) } - /// Returns true if the asset and all of its dependencies (recursive) have been loaded. + /// Convenience method that returns true if the asset has been loaded. + pub fn is_loaded(&self, id: impl Into) -> bool { + matches!(self.load_state(id), LoadState::Loaded) + } + + /// Convenience method that returns true if the asset and all of its direct dependencies have been loaded. + pub fn is_loaded_with_direct_dependencies(&self, id: impl Into) -> bool { + matches!( + self.get_load_states(id), + Some((LoadState::Loaded, DependencyLoadState::Loaded, _)) + ) + } + + /// Convenience method that returns true if the asset, all of its dependencies, and all of its recursive + /// dependencies have been loaded. pub fn is_loaded_with_dependencies(&self, id: impl Into) -> bool { - let id = id.into(); - self.load_state(id) == LoadState::Loaded - && self.recursive_dependency_load_state(id) == RecursiveDependencyLoadState::Loaded + matches!( + self.get_load_states(id), + Some(( + LoadState::Loaded, + DependencyLoadState::Loaded, + RecursiveDependencyLoadState::Loaded + )) + ) } /// Returns an active handle for the given path, if the asset at the given path has already started loading, @@ -1360,12 +1420,14 @@ pub enum LoadState { Loading, /// The asset has been loaded and has been added to the [`World`] Loaded, - /// The asset failed to load. - Failed(Box), + /// The asset failed to load. The underlying [`AssetLoadError`] is + /// referenced by [`Arc`] clones in all related [`DependencyLoadState`]s + /// and [`RecursiveDependencyLoadState`]s in the asset's dependency tree. + Failed(Arc), } /// The load state of an asset's dependencies. -#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Component, Clone, Debug, Eq, PartialEq)] pub enum DependencyLoadState { /// The asset has not started loading yet NotLoaded, @@ -1373,12 +1435,14 @@ pub enum DependencyLoadState { Loading, /// Dependencies have all loaded Loaded, - /// One or more dependencies have failed to load - Failed, + /// One or more dependencies have failed to load. The underlying [`AssetLoadError`] + /// is referenced by [`Arc`] clones in all related [`LoadState`] and + /// [`RecursiveDependencyLoadState`]s in the asset's dependency tree. + Failed(Arc), } /// The recursive load state of an asset's dependencies. -#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Component, Clone, Debug, Eq, PartialEq)] pub enum RecursiveDependencyLoadState { /// The asset has not started loading yet NotLoaded, @@ -1386,8 +1450,11 @@ pub enum RecursiveDependencyLoadState { Loading, /// Dependencies in this asset's dependency tree have all loaded Loaded, - /// One or more dependencies have failed to load in this asset's dependency tree - Failed, + /// One or more dependencies have failed to load in this asset's dependency + /// tree. The underlying [`AssetLoadError`] is referenced by [`Arc`] clones + /// in all related [`LoadState`]s and [`DependencyLoadState`]s in the asset's + /// dependency tree. + Failed(Arc), } /// An error that occurs during an [`Asset`] load. diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 1b2cd92991..f13d81ca67 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -4,11 +4,15 @@ use bevy_utils::{ConditionalSendFuture, HashMap}; use serde::{Deserialize, Serialize}; use std::{ borrow::Borrow, + convert::Infallible, hash::Hash, + marker::PhantomData, ops::{Deref, DerefMut}, }; /// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type. +/// +/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows. pub trait AssetTransformer: Send + Sync + 'static { /// The [`Asset`] type which this [`AssetTransformer`] takes as and input. type AssetInput: Asset; @@ -241,3 +245,37 @@ impl<'a, A: Asset> TransformedSubAsset<'a, A> { self.labeled_assets.keys().map(|s| &**s) } } + +/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.] +pub struct IdentityAssetTransformer { + _phantom: PhantomData A>, +} + +impl IdentityAssetTransformer { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl Default for IdentityAssetTransformer { + fn default() -> Self { + Self::new() + } +} + +impl AssetTransformer for IdentityAssetTransformer { + type AssetInput = A; + type AssetOutput = A; + type Settings = (); + type Error = Infallible; + + async fn transform<'a>( + &'a self, + asset: TransformedAsset, + _settings: &'a Self::Settings, + ) -> Result, Self::Error> { + Ok(asset) + } +} diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 497adfcdba..ec50201a16 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -48,7 +48,7 @@ pub enum PlaybackMode { /// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink] /// components. Changes to this component will *not* be applied to already-playing audio. #[derive(Component, Clone, Copy, Debug, Reflect)] -#[reflect(Default, Component)] +#[reflect(Default, Component, Debug)] pub struct PlaybackSettings { /// The desired playback behavior. pub mode: PlaybackMode, @@ -144,7 +144,7 @@ impl PlaybackSettings { /// This must be accompanied by `Transform` and `GlobalTransform`. /// Only one entity with a `SpatialListener` should be present at any given time. #[derive(Component, Clone, Debug, Reflect)] -#[reflect(Default, Component)] +#[reflect(Default, Component, Debug)] pub struct SpatialListener { /// Left ear position relative to the `GlobalTransform`. pub left_ear_offset: Vec3, @@ -175,7 +175,7 @@ impl SpatialListener { /// /// Note: changing this value will not affect already playing audio. #[derive(Resource, Default, Clone, Copy, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Default)] pub struct GlobalVolume { /// The global volume of all audio. pub volume: Volume, @@ -223,7 +223,7 @@ impl Default for SpatialScale { /// /// Default is `Vec3::ONE`. #[derive(Resource, Default, Clone, Copy, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Default)] pub struct DefaultSpatialScale(pub SpatialScale); /// Bundle for playing a standard bevy audio asset diff --git a/crates/bevy_color/src/laba.rs b/crates/bevy_color/src/laba.rs index 391bb9b4fe..39ac37f8ff 100644 --- a/crates/bevy_color/src/laba.rs +++ b/crates/bevy_color/src/laba.rs @@ -2,7 +2,7 @@ use crate::{ impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza, }; -use bevy_math::{Vec3, Vec4}; +use bevy_math::{ops, Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -225,7 +225,7 @@ impl From for Xyza { let fx = a / 500.0 + fy; let fz = fy - b / 200.0; let xr = { - let fx3 = fx.powf(3.0); + let fx3 = ops::powf(fx, 3.0); if fx3 > Laba::CIE_EPSILON { fx3 @@ -234,12 +234,12 @@ impl From for Xyza { } }; let yr = if l > Laba::CIE_EPSILON * Laba::CIE_KAPPA { - ((l + 16.0) / 116.0).powf(3.0) + ops::powf((l + 16.0) / 116.0, 3.0) } else { l / Laba::CIE_KAPPA }; let zr = { - let fz3 = fz.powf(3.0); + let fz3 = ops::powf(fz, 3.0); if fz3 > Laba::CIE_EPSILON { fz3 @@ -262,17 +262,17 @@ impl From for Laba { let yr = y / Xyza::D65_WHITE.y; let zr = z / Xyza::D65_WHITE.z; let fx = if xr > Laba::CIE_EPSILON { - xr.cbrt() + ops::cbrt(xr) } else { (Laba::CIE_KAPPA * xr + 16.0) / 116.0 }; let fy = if yr > Laba::CIE_EPSILON { - yr.cbrt() + ops::cbrt(yr) } else { (Laba::CIE_KAPPA * yr + 16.0) / 116.0 }; let fz = if yr > Laba::CIE_EPSILON { - zr.cbrt() + ops::cbrt(zr) } else { (Laba::CIE_KAPPA * zr + 16.0) / 116.0 }; diff --git a/crates/bevy_color/src/lcha.rs b/crates/bevy_color/src/lcha.rs index e372ff492a..f1437d3496 100644 --- a/crates/bevy_color/src/lcha.rs +++ b/crates/bevy_color/src/lcha.rs @@ -2,7 +2,7 @@ use crate::{ Alpha, ColorToComponents, Gray, Hue, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza, }; -use bevy_math::{Vec3, Vec4}; +use bevy_math::{ops, Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -257,8 +257,9 @@ impl From for Laba { ) -> Self { // Based on http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html let l = lightness; - let a = chroma * hue.to_radians().cos(); - let b = chroma * hue.to_radians().sin(); + let (sin, cos) = ops::sin_cos(hue.to_radians()); + let a = chroma * cos; + let b = chroma * sin; Laba::new(l, a, b, alpha) } @@ -274,9 +275,9 @@ impl From for Lcha { }: Laba, ) -> Self { // Based on http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html - let c = (a.powf(2.0) + b.powf(2.0)).sqrt(); + let c = ops::hypot(a, b); let h = { - let h = b.to_radians().atan2(a.to_radians()).to_degrees(); + let h = ops::atan2(b.to_radians(), a.to_radians()).to_degrees(); if h < 0.0 { h + 360.0 diff --git a/crates/bevy_color/src/oklaba.rs b/crates/bevy_color/src/oklaba.rs index bf0ffdba16..0ffb35ddd3 100644 --- a/crates/bevy_color/src/oklaba.rs +++ b/crates/bevy_color/src/oklaba.rs @@ -2,7 +2,7 @@ use crate::{ color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza, }; -use bevy_math::{Vec3, Vec4}; +use bevy_math::{ops, FloatPow, Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -156,9 +156,9 @@ impl Luminance for Oklaba { impl EuclideanDistance for Oklaba { #[inline] fn distance_squared(&self, other: &Self) -> f32 { - (self.lightness - other.lightness).powi(2) - + (self.a - other.a).powi(2) - + (self.b - other.b).powi(2) + (self.lightness - other.lightness).squared() + + (self.a - other.a).squared() + + (self.b - other.b).squared() } } @@ -229,9 +229,9 @@ impl From for Oklaba { let l = 0.4122214708 * red + 0.5363325363 * green + 0.0514459929 * blue; let m = 0.2119034982 * red + 0.6806995451 * green + 0.1073969566 * blue; let s = 0.0883024619 * red + 0.2817188376 * green + 0.6299787005 * blue; - let l_ = l.cbrt(); - let m_ = m.cbrt(); - let s_ = s.cbrt(); + let l_ = ops::cbrt(l); + let m_ = ops::cbrt(m); + let s_ = ops::cbrt(s); let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; diff --git a/crates/bevy_color/src/oklcha.rs b/crates/bevy_color/src/oklcha.rs index 165fa0af95..70c150ed0f 100644 --- a/crates/bevy_color/src/oklcha.rs +++ b/crates/bevy_color/src/oklcha.rs @@ -2,7 +2,7 @@ use crate::{ color_difference::EuclideanDistance, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza, }; -use bevy_math::{Vec3, Vec4}; +use bevy_math::{ops, FloatPow, Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; @@ -191,9 +191,9 @@ impl Luminance for Oklcha { impl EuclideanDistance for Oklcha { #[inline] fn distance_squared(&self, other: &Self) -> f32 { - (self.lightness - other.lightness).powi(2) - + (self.chroma - other.chroma).powi(2) - + (self.hue - other.hue).powi(2) + (self.lightness - other.lightness).squared() + + (self.chroma - other.chroma).squared() + + (self.hue - other.hue).squared() } } @@ -260,8 +260,8 @@ impl From for Oklcha { alpha, }: Oklaba, ) -> Self { - let chroma = a.hypot(b); - let hue = b.atan2(a).to_degrees(); + let chroma = ops::hypot(a, b); + let hue = ops::atan2(b, a).to_degrees(); let hue = if hue < 0.0 { hue + 360.0 } else { hue }; @@ -279,8 +279,9 @@ impl From for Oklaba { }: Oklcha, ) -> Self { let l = lightness; - let a = chroma * hue.to_radians().cos(); - let b = chroma * hue.to_radians().sin(); + let (sin, cos) = ops::sin_cos(hue.to_radians()); + let a = chroma * cos; + let b = chroma * sin; Oklaba::new(l, a, b, alpha) } diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index 100d35632d..b235749a64 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -3,7 +3,7 @@ use crate::{ impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba, Luminance, Mix, StandardColor, Xyza, }; -use bevy_math::{Vec3, Vec4}; +use bevy_math::{ops, Vec3, Vec4}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::*; use thiserror::Error; @@ -215,7 +215,7 @@ impl Srgba { if value <= 0.04045 { value / 12.92 // linear falloff in dark values } else { - ((value + 0.055) / 1.055).powf(2.4) // gamma curve in other area + ops::powf((value + 0.055) / 1.055, 2.4) // gamma curve in other area } } @@ -228,7 +228,7 @@ impl Srgba { if value <= 0.0031308 { value * 12.92 // linear falloff in dark values } else { - (1.055 * value.powf(1.0 / 2.4)) - 0.055 // gamma curve in other area + (1.055 * ops::powf(value, 1.0 / 2.4)) - 0.055 // gamma curve in other area } } } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs b/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs index 5a6d4330f2..76698d14c4 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/buffers.rs @@ -6,8 +6,8 @@ use bevy_render::{ }; use bevy_utils::{Entry, HashMap}; -use super::pipeline::AutoExposureSettingsUniform; -use super::AutoExposureSettings; +use super::pipeline::AutoExposureUniform; +use super::AutoExposure; #[derive(Resource, Default)] pub(super) struct AutoExposureBuffers { @@ -16,19 +16,19 @@ pub(super) struct AutoExposureBuffers { pub(super) struct AutoExposureBuffer { pub(super) state: StorageBuffer, - pub(super) settings: UniformBuffer, + pub(super) settings: UniformBuffer, } #[derive(Resource)] pub(super) struct ExtractedStateBuffers { - changed: Vec<(Entity, AutoExposureSettings)>, + changed: Vec<(Entity, AutoExposure)>, removed: Vec, } pub(super) fn extract_buffers( mut commands: Commands, - changed: Extract>>, - mut removed: Extract>, + changed: Extract>>, + mut removed: Extract>, ) { commands.insert_resource(ExtractedStateBuffers { changed: changed @@ -50,7 +50,7 @@ pub(super) fn prepare_buffers( let (low_percent, high_percent) = settings.filter.into_inner(); let initial_state = 0.0f32.clamp(min_log_lum, max_log_lum); - let settings = AutoExposureSettingsUniform { + let settings = AutoExposureUniform { min_log_lum, inv_log_lum_range: 1.0 / (max_log_lum - min_log_lum), log_lum_range: max_log_lum - min_log_lum, diff --git a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs index d50d1451d8..dccd63bff0 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs @@ -26,14 +26,15 @@ use node::AutoExposureNode; use pipeline::{ AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline, METERING_SHADER_HANDLE, }; -pub use settings::AutoExposureSettings; +#[allow(deprecated)] +pub use settings::{AutoExposure, AutoExposureSettings}; use crate::auto_exposure::compensation_curve::GpuAutoExposureCompensationCurve; use crate::core_3d::graph::{Core3d, Node3d}; /// Plugin for the auto exposure feature. /// -/// See [`AutoExposureSettings`] for more details. +/// See [`AutoExposure`] for more details. pub struct AutoExposurePlugin; #[derive(Resource)] @@ -58,8 +59,8 @@ impl Plugin for AutoExposurePlugin { .resource_mut::>() .insert(&Handle::default(), AutoExposureCompensationCurve::default()); - app.register_type::(); - app.add_plugins(ExtractComponentPlugin::::default()); + app.register_type::(); + app.add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -113,9 +114,9 @@ fn queue_view_auto_exposure_pipelines( pipeline_cache: Res, mut compute_pipelines: ResMut>, pipeline: Res, - view_targets: Query<(Entity, &AutoExposureSettings)>, + view_targets: Query<(Entity, &AutoExposure)>, ) { - for (entity, settings) in view_targets.iter() { + for (entity, auto_exposure) in view_targets.iter() { let histogram_pipeline = compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Histogram); let average_pipeline = @@ -124,8 +125,8 @@ fn queue_view_auto_exposure_pipelines( commands.entity(entity).insert(ViewAutoExposurePipeline { histogram_pipeline, mean_luminance_pipeline: average_pipeline, - compensation_curve: settings.compensation_curve.clone(), - metering_mask: settings.metering_mask.clone(), + compensation_curve: auto_exposure.compensation_curve.clone(), + metering_mask: auto_exposure.metering_mask.clone(), }); } } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index 937e18f410..684c8dd9e2 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -27,7 +27,7 @@ pub struct ViewAutoExposurePipeline { } #[derive(ShaderType, Clone, Copy)] -pub struct AutoExposureSettingsUniform { +pub struct AutoExposureUniform { pub(super) min_log_lum: f32, pub(super) inv_log_lum_range: f32, pub(super) log_lum_range: f32, @@ -59,7 +59,7 @@ impl FromWorld for AutoExposurePipeline { ShaderStages::COMPUTE, ( uniform_buffer::(false), - uniform_buffer::(false), + uniform_buffer::(false), texture_2d(TextureSampleType::Float { filterable: false }), texture_2d(TextureSampleType::Float { filterable: false }), texture_1d(TextureSampleType::Float { filterable: false }), diff --git a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs index c5d28c94c4..388a67fb79 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/settings.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/settings.rs @@ -3,6 +3,7 @@ use std::ops::RangeInclusive; use super::compensation_curve::AutoExposureCompensationCurve; use bevy_asset::Handle; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{extract_component::ExtractComponent, texture::Image}; use bevy_utils::default; @@ -24,8 +25,8 @@ use bevy_utils::default; /// **Auto Exposure requires compute shaders and is not compatible with WebGL2.** /// #[derive(Component, Clone, Reflect, ExtractComponent)] -#[reflect(Component)] -pub struct AutoExposureSettings { +#[reflect(Component, Default)] +pub struct AutoExposure { /// The range of exposure values for the histogram. /// /// Pixel values below this range will be ignored, and pixel values above this range will be @@ -88,7 +89,10 @@ pub struct AutoExposureSettings { pub compensation_curve: Handle, } -impl Default for AutoExposureSettings { +#[deprecated(since = "0.15.0", note = "Renamed to `AutoExposure`")] +pub type AutoExposureSettings = AutoExposure; + +impl Default for AutoExposure { fn default() -> Self { Self { range: -8.0..=8.0, diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index ba48e4b0fe..28d55d360a 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -1,4 +1,4 @@ -use super::{BloomSettings, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT}; +use super::{Bloom, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT}; use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_ecs::{ prelude::{Component, Entity}, @@ -33,7 +33,7 @@ pub struct BloomDownsamplingPipelineKeys { first_downsample: bool, } -/// The uniform struct extracted from [`BloomSettings`] attached to a Camera. +/// The uniform struct extracted from [`Bloom`] attached to a Camera. /// Will be available for use in the Bloom shader. #[derive(Component, ShaderType, Clone)] pub struct BloomUniforms { @@ -136,10 +136,10 @@ pub fn prepare_downsampling_pipeline( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &BloomSettings)>, + views: Query<(Entity, &Bloom)>, ) { - for (entity, settings) in &views { - let prefilter = settings.prefilter_settings.threshold > 0.0; + for (entity, bloom) in &views { + let prefilter = bloom.prefilter.threshold > 0.0; let pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index b5c4c6b72c..ff642c1f2f 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -3,7 +3,10 @@ mod settings; mod upsampling_pipeline; use bevy_color::{Gray, LinearRgba}; -pub use settings::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings}; +#[allow(deprecated)] +pub use settings::{ + Bloom, BloomCompositeMode, BloomPrefilter, BloomPrefilterSettings, BloomSettings, +}; use crate::{ core_2d::graph::{Core2d, Node2d}, @@ -12,7 +15,7 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; use bevy_ecs::{prelude::*, query::QueryItem}; -use bevy_math::UVec2; +use bevy_math::{ops, UVec2}; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, @@ -44,11 +47,11 @@ impl Plugin for BloomPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl); - app.register_type::(); - app.register_type::(); + app.register_type::(); + app.register_type::(); app.register_type::(); app.add_plugins(( - ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), )); @@ -100,7 +103,7 @@ impl ViewNode for BloomNode { &'static BloomTexture, &'static BloomBindGroups, &'static DynamicUniformIndex, - &'static BloomSettings, + &'static Bloom, &'static UpsamplingPipelineIds, &'static BloomDownsamplingPipelineIds, ); @@ -324,18 +327,18 @@ fn prepare_bloom_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views: Query<(Entity, &ExtractedCamera, &BloomSettings)>, + views: Query<(Entity, &ExtractedCamera, &Bloom)>, ) { - for (entity, camera, settings) in &views { + for (entity, camera, bloom) in &views { if let Some(UVec2 { x: width, y: height, }) = camera.physical_viewport_size { // How many times we can halve the resolution minus one so we don't go unnecessarily low - let mip_count = settings.max_mip_dimension.ilog2().max(2) - 1; + let mip_count = bloom.max_mip_dimension.ilog2().max(2) - 1; let mip_height_ratio = if height != 0 { - settings.max_mip_dimension as f32 / height as f32 + bloom.max_mip_dimension as f32 / height as f32 } else { 0. }; @@ -457,19 +460,20 @@ fn prepare_bloom_bind_groups( /// * `max_mip` - the index of the lowest frequency pyramid level. /// /// This function can be visually previewed for all values of *mip* (normalized) with tweakable -/// [`BloomSettings`] parameters on [Desmos graphing calculator](https://www.desmos.com/calculator/ncc8xbhzzl). -fn compute_blend_factor(bloom_settings: &BloomSettings, mip: f32, max_mip: f32) -> f32 { - let mut lf_boost = (1.0 - - (1.0 - (mip / max_mip)).powf(1.0 / (1.0 - bloom_settings.low_frequency_boost_curvature))) - * bloom_settings.low_frequency_boost; +/// [`Bloom`] parameters on [Desmos graphing calculator](https://www.desmos.com/calculator/ncc8xbhzzl). +fn compute_blend_factor(bloom: &Bloom, mip: f32, max_mip: f32) -> f32 { + let mut lf_boost = + (1.0 - ops::powf( + 1.0 - (mip / max_mip), + 1.0 / (1.0 - bloom.low_frequency_boost_curvature), + )) * bloom.low_frequency_boost; let high_pass_lq = 1.0 - - (((mip / max_mip) - bloom_settings.high_pass_frequency) - / bloom_settings.high_pass_frequency) + - (((mip / max_mip) - bloom.high_pass_frequency) / bloom.high_pass_frequency) .clamp(0.0, 1.0); - lf_boost *= match bloom_settings.composite_mode { - BloomCompositeMode::EnergyConserving => 1.0 - bloom_settings.intensity, + lf_boost *= match bloom.composite_mode { + BloomCompositeMode::EnergyConserving => 1.0 - bloom.intensity, BloomCompositeMode::Additive => 1.0, }; - (bloom_settings.intensity + lf_boost) * high_pass_lq + (bloom.intensity + lf_boost) * high_pass_lq } diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index 8fe6628214..3fc35dc4d5 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -26,7 +26,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; /// used in Bevy as well as a visualization of the curve's respective scattering profile. #[derive(Component, Reflect, Clone)] #[reflect(Component, Default)] -pub struct BloomSettings { +pub struct Bloom { /// Controls the baseline of how much the image is scattered (default: 0.15). /// /// This parameter should be used only to control the strength of the bloom @@ -90,15 +90,21 @@ pub struct BloomSettings { /// * 1.0 - maximum scattering angle is 90 degrees pub high_pass_frequency: f32, - pub prefilter_settings: BloomPrefilterSettings, + /// Controls the threshold filter used for extracting the brightest regions from the input image + /// before blurring them and compositing back onto the original image. + /// + /// Changing these settings creates a physically inaccurate image and makes it easy to make + /// the final result look worse. However, they can be useful when emulating the 1990s-2000s game look. + /// See [`BloomPrefilter`] for more information. + pub prefilter: BloomPrefilter, /// Controls whether bloom textures /// are blended between or added to each other. Useful /// if image brightening is desired and a must-change - /// if `prefilter_settings` are used. + /// if `prefilter` is used. /// /// # Recommendation - /// Set to [`BloomCompositeMode::Additive`] if `prefilter_settings` are + /// Set to [`BloomCompositeMode::Additive`] if `prefilter` is /// configured in a non-energy-conserving way, /// otherwise set to [`BloomCompositeMode::EnergyConserving`]. pub composite_mode: BloomCompositeMode, @@ -112,7 +118,10 @@ pub struct BloomSettings { pub uv_offset: f32, } -impl BloomSettings { +#[deprecated(since = "0.15.0", note = "Renamed to `Bloom`")] +pub type BloomSettings = Bloom; + +impl Bloom { const DEFAULT_MAX_MIP_DIMENSION: u32 = 512; const DEFAULT_UV_OFFSET: f32 = 0.004; @@ -124,7 +133,7 @@ impl BloomSettings { low_frequency_boost: 0.7, low_frequency_boost_curvature: 0.95, high_pass_frequency: 1.0, - prefilter_settings: BloomPrefilterSettings { + prefilter: BloomPrefilter { threshold: 0.0, threshold_softness: 0.0, }, @@ -139,7 +148,7 @@ impl BloomSettings { low_frequency_boost: 0.7, low_frequency_boost_curvature: 0.95, high_pass_frequency: 1.0, - prefilter_settings: BloomPrefilterSettings { + prefilter: BloomPrefilter { threshold: 0.6, threshold_softness: 0.2, }, @@ -154,7 +163,7 @@ impl BloomSettings { low_frequency_boost: 0.0, low_frequency_boost_curvature: 0.0, high_pass_frequency: 1.0 / 3.0, - prefilter_settings: BloomPrefilterSettings { + prefilter: BloomPrefilter { threshold: 0.0, threshold_softness: 0.0, }, @@ -164,7 +173,7 @@ impl BloomSettings { }; } -impl Default for BloomSettings { +impl Default for Bloom { fn default() -> Self { Self::NATURAL } @@ -179,7 +188,7 @@ impl Default for BloomSettings { /// * Changing these settings makes it easy to make the final result look worse /// * Non-default prefilter settings should be used in conjunction with [`BloomCompositeMode::Additive`] #[derive(Default, Clone, Reflect)] -pub struct BloomPrefilterSettings { +pub struct BloomPrefilter { /// Baseline of the quadratic threshold curve (default: 0.0). /// /// RGB values under the threshold curve will not contribute to the effect. @@ -194,19 +203,22 @@ pub struct BloomPrefilterSettings { pub threshold_softness: f32, } +#[deprecated(since = "0.15.0", note = "Renamed to `BloomPrefilter`")] +pub type BloomPrefilterSettings = BloomPrefilter; + #[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)] pub enum BloomCompositeMode { EnergyConserving, Additive, } -impl ExtractComponent for BloomSettings { +impl ExtractComponent for Bloom { type QueryData = (&'static Self, &'static Camera); type QueryFilter = (); type Out = (Self, BloomUniforms); - fn extract_component((settings, camera): QueryItem<'_, Self::QueryData>) -> Option { + fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option { match ( camera.physical_viewport_rect(), camera.physical_viewport_size(), @@ -215,8 +227,8 @@ impl ExtractComponent for BloomSettings { camera.hdr, ) { (Some(URect { min: origin, .. }), Some(size), Some(target_size), true, true) => { - let threshold = settings.prefilter_settings.threshold; - let threshold_softness = settings.prefilter_settings.threshold_softness; + let threshold = bloom.prefilter.threshold; + let threshold_softness = bloom.prefilter.threshold_softness; let knee = threshold * threshold_softness.clamp(0.0, 1.0); let uniform = BloomUniforms { @@ -229,11 +241,13 @@ impl ExtractComponent for BloomSettings { viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4() / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y) .as_vec4(), - aspect: AspectRatio::from_pixels(size.x, size.y).into(), - uv_offset: settings.uv_offset, + aspect: AspectRatio::try_from_pixels(size.x, size.y) + .expect("Valid screen size values for Bloom settings") + .ratio(), + uv_offset: bloom.uv_offset, }; - Some((settings.clone(), uniform)) + Some((bloom.clone(), uniform)) } _ => None, } diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index 161c251e36..91a23310ef 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -1,5 +1,5 @@ use super::{ - downsampling_pipeline::BloomUniforms, BloomCompositeMode, BloomSettings, BLOOM_SHADER_HANDLE, + downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT, }; use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; @@ -133,14 +133,14 @@ pub fn prepare_upsampling_pipeline( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &BloomSettings)>, + views: Query<(Entity, &Bloom)>, ) { - for (entity, settings) in &views { + for (entity, bloom) in &views { let pipeline_id = pipelines.specialize( &pipeline_cache, &pipeline, BloomUpsamplingPipelineKeys { - composite_mode: settings.composite_mode, + composite_mode: bloom.composite_mode, final_pipeline: false, }, ); @@ -149,7 +149,7 @@ pub fn prepare_upsampling_pipeline( &pipeline_cache, &pipeline, BloomUpsamplingPipelineKeys { - composite_mode: settings.composite_mode, + composite_mode: bloom.composite_mode, final_pipeline: true, }, ); 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 f76e063672..5afde16797 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs @@ -6,6 +6,7 @@ use crate::{ use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, Handle}; use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, @@ -34,10 +35,10 @@ pub use node::CasNode; /// based on the local contrast. This can help avoid over-sharpening areas with high contrast /// and under-sharpening areas with low contrast. /// -/// To use this, add the [`ContrastAdaptiveSharpeningSettings`] component to a 2D or 3D camera. +/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera. #[derive(Component, Reflect, Clone)] -#[reflect(Component)] -pub struct ContrastAdaptiveSharpeningSettings { +#[reflect(Component, Default)] +pub struct ContrastAdaptiveSharpening { /// Enable or disable sharpening. pub enabled: bool, /// Adjusts sharpening strength. Higher values increase the amount of sharpening. @@ -54,9 +55,12 @@ pub struct ContrastAdaptiveSharpeningSettings { pub denoise: bool, } -impl Default for ContrastAdaptiveSharpeningSettings { +#[deprecated(since = "0.15.0", note = "Renamed to `ContrastAdaptiveSharpening`")] +pub type ContrastAdaptiveSharpeningSettings = ContrastAdaptiveSharpening; + +impl Default for ContrastAdaptiveSharpening { fn default() -> Self { - ContrastAdaptiveSharpeningSettings { + ContrastAdaptiveSharpening { enabled: true, sharpening_strength: 0.6, denoise: false, @@ -65,10 +69,10 @@ impl Default for ContrastAdaptiveSharpeningSettings { } #[derive(Component, Default, Reflect, Clone)] -#[reflect(Component)] +#[reflect(Component, Default)] pub struct DenoiseCas(bool); -/// The uniform struct extracted from [`ContrastAdaptiveSharpeningSettings`] attached to a [`Camera`]. +/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`]. /// Will be available for use in the CAS shader. #[doc(hidden)] #[derive(Component, ShaderType, Clone)] @@ -76,7 +80,7 @@ pub struct CasUniform { sharpness: f32, } -impl ExtractComponent for ContrastAdaptiveSharpeningSettings { +impl ExtractComponent for ContrastAdaptiveSharpening { type QueryData = &'static Self; type QueryFilter = With; type Out = (DenoiseCas, CasUniform); @@ -110,9 +114,9 @@ impl Plugin for CasPlugin { Shader::from_wgsl ); - app.register_type::(); + app.register_type::(); app.add_plugins(( - ExtractComponentPlugin::::default(), + ExtractComponentPlugin::::default(), UniformComponentPlugin::::default(), )); @@ -241,12 +245,12 @@ fn prepare_cas_pipelines( sharpening_pipeline: Res, views: Query<(Entity, &ExtractedView, &DenoiseCas), With>, ) { - for (entity, view, cas_settings) in &views { + for (entity, view, cas) in &views { let pipeline_id = pipelines.specialize( &pipeline_cache, &sharpening_pipeline, CasPipelineKey { - denoise: cas_settings.0, + denoise: cas.0, texture_format: if view.hdr { ViewTarget::TEXTURE_FORMAT_HDR } else { diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 857c220216..b2181bdddc 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -1,6 +1,7 @@ use crate::core_2d::graph::Core2d; use crate::tonemapping::{DebandDither, Tonemapping}; use bevy_ecs::prelude::*; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::prelude::Msaa; use bevy_render::{ @@ -16,17 +17,13 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; #[derive(Component, Default, Reflect, Clone, ExtractComponent)] #[extract_component_filter(With)] -#[reflect(Component)] +#[reflect(Component, Default)] pub struct Camera2d; #[derive(Bundle, Clone)] pub struct Camera2dBundle { pub camera: Camera, pub camera_render_graph: CameraRenderGraph, - /// Note: default value for `OrthographicProjection.near` is `0.0` - /// which makes objects on the screen plane invisible to 2D camera. - /// `Camera2dBundle::default()` sets `near` to negative value, - /// so be careful when initializing this field manually. pub projection: OrthographicProjection, pub visible_entities: VisibleEntities, pub frustum: Frustum, @@ -41,11 +38,7 @@ pub struct Camera2dBundle { impl Default for Camera2dBundle { fn default() -> Self { - let projection = OrthographicProjection { - far: 1000., - near: -1000., - ..Default::default() - }; + let projection = OrthographicProjection::default_2d(); let transform = Transform::default(); let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); Self { @@ -77,7 +70,7 @@ impl Camera2dBundle { // the camera's translation by far and use a right handed coordinate system let projection = OrthographicProjection { far, - ..Default::default() + ..OrthographicProjection::default_2d() }; let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 697d9df64b..c2f703a784 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -3,6 +3,7 @@ use crate::{ tonemapping::{DebandDither, Tonemapping}, }; use bevy_ecs::prelude::*; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::view::Msaa; use bevy_render::{ @@ -20,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// This means "forward" is -Z. #[derive(Component, Reflect, Clone, ExtractComponent)] #[extract_component_filter(With)] -#[reflect(Component)] +#[reflect(Component, Default)] pub struct Camera3d { /// The depth clear operation to perform for the main 3d pass. pub depth_load_op: Camera3dDepthLoadOp, @@ -112,7 +113,7 @@ impl From for LoadOp { /// /// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin). #[derive(Resource, Default, Clone, Copy, Reflect, PartialEq, PartialOrd, Debug)] -#[reflect(Resource)] +#[reflect(Resource, Default, Debug, PartialEq)] pub enum ScreenSpaceTransmissionQuality { /// Best performance at the cost of quality. Suitable for lower end GPUs. (e.g. Mobile) /// diff --git a/crates/bevy_core_pipeline/src/dof/dof.wgsl b/crates/bevy_core_pipeline/src/dof/dof.wgsl index e1ea4c22f1..2af11077f8 100644 --- a/crates/bevy_core_pipeline/src/dof/dof.wgsl +++ b/crates/bevy_core_pipeline/src/dof/dof.wgsl @@ -52,7 +52,7 @@ struct DepthOfFieldParams { max_circle_of_confusion_diameter: f32, /// The depth value that we clamp distant objects to. See the comment in - /// [`DepthOfFieldSettings`] for more information. + /// [`DepthOfField`] for more information. max_depth: f32, /// Padding. diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index a35bdc0723..ba31dfb279 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -7,7 +7,7 @@ //! [depth of field], and this term is used more generally in computer graphics //! to refer to the effect that simulates focus of lenses. //! -//! Attaching [`DepthOfFieldSettings`] to a camera causes Bevy to simulate the +//! Attaching [`DepthOfField`] to a camera causes Bevy to simulate the //! focus of a camera lens. Generally, Bevy's implementation of depth of field //! is optimized for speed instead of physical accuracy. Nevertheless, the depth //! of field effect in Bevy is based on physical parameters. @@ -26,6 +26,7 @@ use bevy_ecs::{ system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; +use bevy_math::ops; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ camera::{PhysicalCameraParameters, Projection}, @@ -68,10 +69,13 @@ const DOF_SHADER_HANDLE: Handle = Handle::weak_from_u128(203186118073921 /// A plugin that adds support for the depth of field effect to Bevy. pub struct DepthOfFieldPlugin; -/// Depth of field settings. +/// A component that enables a [depth of field] postprocessing effect when attached to a [`Camera3d`], +/// simulating the focus of a camera lens. +/// +/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field #[derive(Component, Clone, Copy, Reflect)] #[reflect(Component, Default)] -pub struct DepthOfFieldSettings { +pub struct DepthOfField { /// The appearance of the effect. pub mode: DepthOfFieldMode, @@ -112,6 +116,9 @@ pub struct DepthOfFieldSettings { pub max_depth: f32, } +#[deprecated(since = "0.15.0", note = "Renamed to `DepthOfField`")] +pub type DepthOfFieldSettings = DepthOfField; + /// Controls the appearance of the effect. #[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)] #[reflect(Default, PartialEq)] @@ -156,11 +163,11 @@ pub struct DepthOfFieldUniform { coc_scale_factor: f32, /// The maximum circle of confusion diameter in pixels. See the comment in - /// [`DepthOfFieldSettings`] for more information. + /// [`DepthOfField`] for more information. max_circle_of_confusion_diameter: f32, /// The depth value that we clamp distant objects to. See the comment in - /// [`DepthOfFieldSettings`] for more information. + /// [`DepthOfField`] for more information. max_depth: f32, /// Padding. @@ -199,7 +206,7 @@ impl Plugin for DepthOfFieldPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, DOF_SHADER_HANDLE, "dof.wgsl", Shader::from_wgsl); - app.register_type::(); + app.register_type::(); app.register_type::(); app.add_plugins(UniformComponentPlugin::::default()); @@ -339,7 +346,7 @@ impl ViewNode for DepthOfFieldNode { view_depth_texture, view_pipelines, view_bind_group_layouts, - dof_settings_uniform_index, + depth_of_field_uniform_index, auxiliary_dof_texture, ): QueryItem<'w, Self::ViewQuery>, world: &'w World, @@ -440,7 +447,11 @@ impl ViewNode for DepthOfFieldNode { // Set the per-view bind group. render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]); // Set the global bind group shared among all invocations of the shader. - render_pass.set_bind_group(1, global_bind_group, &[dof_settings_uniform_index.index()]); + render_pass.set_bind_group( + 1, + global_bind_group, + &[depth_of_field_uniform_index.index()], + ); // Render the full-screen pass. render_pass.draw(0..3, 0..1); } @@ -449,7 +460,7 @@ impl ViewNode for DepthOfFieldNode { } } -impl Default for DepthOfFieldSettings { +impl Default for DepthOfField { fn default() -> Self { let physical_camera_default = PhysicalCameraParameters::default(); Self { @@ -463,8 +474,8 @@ impl Default for DepthOfFieldSettings { } } -impl DepthOfFieldSettings { - /// Initializes [`DepthOfFieldSettings`] from a set of +impl DepthOfField { + /// Initializes [`DepthOfField`] from a set of /// [`PhysicalCameraParameters`]. /// /// By passing the same [`PhysicalCameraParameters`] object to this function @@ -472,10 +483,10 @@ impl DepthOfFieldSettings { /// results for both the exposure and depth of field effects can be /// obtained. /// - /// All fields of the returned [`DepthOfFieldSettings`] other than + /// All fields of the returned [`DepthOfField`] other than /// `focal_length` and `aperture_f_stops` are set to their default values. - pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfFieldSettings { - DepthOfFieldSettings { + pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField { + DepthOfField { sensor_height: camera.sensor_height, aperture_f_stops: camera.aperture_f_stops, ..default() @@ -521,10 +532,10 @@ impl FromWorld for DepthOfFieldGlobalBindGroupLayout { /// specific to each view. pub fn prepare_depth_of_field_view_bind_group_layouts( mut commands: Commands, - view_targets: Query<(Entity, &DepthOfFieldSettings, &Msaa)>, + view_targets: Query<(Entity, &DepthOfField, &Msaa)>, render_device: Res, ) { - for (view, dof_settings, msaa) in view_targets.iter() { + for (view, depth_of_field, msaa) in view_targets.iter() { // Create the bind group layout for the passes that take one input. let single_input = render_device.create_bind_group_layout( Some("depth of field bind group layout (single input)"), @@ -544,7 +555,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts( // If needed, create the bind group layout for the second bokeh pass, // which takes two inputs. We only need to do this if bokeh is in use. - let dual_input = match dof_settings.mode { + let dual_input = match depth_of_field.mode { DepthOfFieldMode::Gaussian => None, DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout( Some("depth of field bind group layout (dual input)"), @@ -581,7 +592,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts( /// need to set the appropriate flag to tell Bevy to make samplable depth /// buffers. pub fn configure_depth_of_field_view_targets( - mut view_targets: Query<&mut Camera3d, With>, + mut view_targets: Query<&mut Camera3d, With>, ) { for mut camera_3d in view_targets.iter_mut() { let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages); @@ -595,10 +606,10 @@ pub fn configure_depth_of_field_view_targets( pub fn prepare_depth_of_field_global_bind_group( global_bind_group_layout: Res, mut dof_bind_group: ResMut, - dof_settings_uniforms: Res>, + depth_of_field_uniforms: Res>, render_device: Res, ) { - let Some(dof_settings_uniforms) = dof_settings_uniforms.binding() else { + let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else { return; }; @@ -606,7 +617,7 @@ pub fn prepare_depth_of_field_global_bind_group( Some("depth of field global bind group"), &global_bind_group_layout.layout, &BindGroupEntries::sequential(( - dof_settings_uniforms, // `dof_params` + depth_of_field_uniforms, // `dof_params` &global_bind_group_layout.color_texture_sampler, // `color_texture_sampler` )), )); @@ -618,11 +629,11 @@ pub fn prepare_auxiliary_depth_of_field_textures( mut commands: Commands, render_device: Res, mut texture_cache: ResMut, - mut view_targets: Query<(Entity, &ViewTarget, &DepthOfFieldSettings)>, + mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>, ) { - for (entity, view_target, dof_settings) in view_targets.iter_mut() { + for (entity, view_target, depth_of_field) in view_targets.iter_mut() { // An auxiliary texture is only needed for bokeh. - if dof_settings.mode != DepthOfFieldMode::Bokeh { + if depth_of_field.mode != DepthOfFieldMode::Bokeh { continue; } @@ -655,12 +666,12 @@ pub fn prepare_depth_of_field_pipelines( view_targets: Query<( Entity, &ExtractedView, - &DepthOfFieldSettings, + &DepthOfField, &ViewDepthOfFieldBindGroupLayouts, &Msaa, )>, ) { - for (entity, view, dof_settings, view_bind_group_layouts, msaa) in view_targets.iter() { + for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() { let dof_pipeline = DepthOfFieldPipeline { view_bind_group_layouts: view_bind_group_layouts.clone(), global_bind_group_layout: global_bind_group_layout.layout.clone(), @@ -670,7 +681,7 @@ pub fn prepare_depth_of_field_pipelines( let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off); // Go ahead and specialize the pipelines. - match dof_settings.mode { + match depth_of_field.mode { DepthOfFieldMode::Gaussian => { commands .entity(entity) @@ -795,10 +806,10 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline { } } -/// Extracts all [`DepthOfFieldSettings`] components into the render world. +/// Extracts all [`DepthOfField`] components into the render world. fn extract_depth_of_field_settings( mut commands: Commands, - mut query: Extract>, + mut query: Extract>, ) { if !DEPTH_TEXTURE_SAMPLING_SUPPORTED { info_once!( @@ -807,25 +818,25 @@ fn extract_depth_of_field_settings( return; } - for (entity, dof_settings, projection) in query.iter_mut() { + for (entity, depth_of_field, projection) in query.iter_mut() { // Depth of field is nonsensical without a perspective projection. let Projection::Perspective(ref perspective_projection) = *projection else { continue; }; let focal_length = - calculate_focal_length(dof_settings.sensor_height, perspective_projection.fov); + calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov); - // Convert `DepthOfFieldSettings` to `DepthOfFieldUniform`. + // Convert `DepthOfField` to `DepthOfFieldUniform`. commands.get_or_spawn(entity).insert(( - *dof_settings, + *depth_of_field, DepthOfFieldUniform { - focal_distance: dof_settings.focal_distance, + focal_distance: depth_of_field.focal_distance, focal_length, coc_scale_factor: focal_length * focal_length - / (dof_settings.sensor_height * dof_settings.aperture_f_stops), - max_circle_of_confusion_diameter: dof_settings.max_circle_of_confusion_diameter, - max_depth: dof_settings.max_depth, + / (depth_of_field.sensor_height * depth_of_field.aperture_f_stops), + max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter, + max_depth: depth_of_field.max_depth, pad_a: 0, pad_b: 0, pad_c: 0, @@ -838,7 +849,7 @@ fn extract_depth_of_field_settings( /// /// See . pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 { - 0.5 * sensor_height / f32::tan(0.5 * fov) + 0.5 * sensor_height / ops::tan(0.5 * fov) } impl DepthOfFieldPipelines { diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index f338a56437..85fe16bd48 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -49,9 +49,12 @@ impl Sensitivity { } } +/// A component for enabling Fast Approximate Anti-Aliasing (FXAA) +/// for a [`bevy_render::camera::Camera`]. #[derive(Reflect, Component, Clone, ExtractComponent)] #[reflect(Component, Default)] #[extract_component_filter(With)] +#[doc(alias = "FastApproximateAntiAliasing")] pub struct Fxaa { /// Enable render passes for FXAA. pub enabled: bool, @@ -60,7 +63,7 @@ pub struct Fxaa { /// Use higher sensitivity for a slower, smoother, result. /// [`Ultra`](`Sensitivity::Ultra`) and [`Extreme`](`Sensitivity::Extreme`) /// settings can result in significant smearing and loss of detail. - + /// /// The minimum amount of local contrast required to apply algorithm. pub edge_threshold: Sensitivity, diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index ec77241466..98c00c0c74 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -34,9 +34,10 @@ pub use skybox::Skybox; /// Expect bugs, missing features, compatibility issues, low performance, and/or future breaking changes. pub mod experimental { pub mod taa { + #[allow(deprecated)] pub use crate::taa::{ TemporalAntiAliasBundle, TemporalAntiAliasNode, TemporalAntiAliasPlugin, - TemporalAntiAliasSettings, + TemporalAntiAliasSettings, TemporalAntiAliasing, }; } } diff --git a/crates/bevy_core_pipeline/src/motion_blur/node.rs b/crates/bevy_core_pipeline/src/motion_blur/node.rs index 24f470c563..2497bd633d 100644 --- a/crates/bevy_core_pipeline/src/motion_blur/node.rs +++ b/crates/bevy_core_pipeline/src/motion_blur/node.rs @@ -33,10 +33,10 @@ impl ViewNode for MotionBlurNode { &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (view_target, pipeline_id, prepass_textures, settings, msaa): QueryItem, + (view_target, pipeline_id, prepass_textures, motion_blur, msaa): QueryItem, world: &World, ) -> Result<(), NodeRunError> { - if settings.samples == 0 || settings.shutter_angle <= 0.0 { + if motion_blur.samples == 0 || motion_blur.shutter_angle <= 0.0 { return Ok(()); // We can skip running motion blur in these cases. } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 1f03eb8220..bebb2a180f 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -32,6 +32,7 @@ use std::ops::Range; use bevy_asset::UntypedAssetId; use bevy_ecs::prelude::*; use bevy_math::Mat4; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ render_phase::{ @@ -52,20 +53,24 @@ pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float /// If added to a [`crate::prelude::Camera3d`] then depth values will be copied to a separate texture available to the main pass. #[derive(Component, Default, Reflect, Clone)] +#[reflect(Component, Default)] pub struct DepthPrepass; /// If added to a [`crate::prelude::Camera3d`] then vertex world normals will be copied to a separate texture available to the main pass. /// Normals will have normal map textures already applied. #[derive(Component, Default, Reflect, Clone)] +#[reflect(Component, Default)] pub struct NormalPrepass; /// If added to a [`crate::prelude::Camera3d`] then screen space motion vectors will be copied to a separate texture available to the main pass. #[derive(Component, Default, Reflect, Clone)] +#[reflect(Component, Default)] pub struct MotionVectorPrepass; /// If added to a [`crate::prelude::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes. /// Note the default deferred lighting plugin also requires `DepthPrepass` to work correctly. #[derive(Component, Default, Reflect)] +#[reflect(Component, Default)] pub struct DeferredPrepass; #[derive(Component, ShaderType, Clone)] diff --git a/crates/bevy_core_pipeline/src/smaa/mod.rs b/crates/bevy_core_pipeline/src/smaa/mod.rs index e6e00e54b0..a41a77c806 100644 --- a/crates/bevy_core_pipeline/src/smaa/mod.rs +++ b/crates/bevy_core_pipeline/src/smaa/mod.rs @@ -11,7 +11,7 @@ //! which have made SMAA less popular when advanced photorealistic rendering //! features are used in recent years. //! -//! To use SMAA, add [`SmaaSettings`] to a [`bevy_render::camera::Camera`]. In a +//! To use SMAA, add [`Smaa`] to a [`bevy_render::camera::Camera`]. In a //! pinch, you can simply use the default settings (via the [`Default`] trait) //! for a high-quality, high-performance appearance. When using SMAA, you will //! likely want set [`bevy_render::view::Msaa`] to [`bevy_render::view::Msaa::Off`] @@ -95,17 +95,21 @@ const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle = Handle::weak_from_u128(318 /// Adds support for subpixel morphological antialiasing, or SMAA. pub struct SmaaPlugin; -/// Add this component to a [`bevy_render::camera::Camera`] to enable subpixel -/// morphological antialiasing (SMAA). +/// A component for enabling Subpixel Morphological Anti-Aliasing (SMAA) +/// for a [`bevy_render::camera::Camera`]. #[derive(Clone, Copy, Default, Component, Reflect, ExtractComponent)] #[reflect(Component, Default)] -pub struct SmaaSettings { +#[doc(alias = "SubpixelMorphologicalAntiAliasing")] +pub struct Smaa { /// A predefined set of SMAA parameters: i.e. a quality level. /// /// Generally, you can leave this at its default level. pub preset: SmaaPreset, } +#[deprecated(since = "0.15.0", note = "Renamed to `Smaa`")] +pub type SmaaSettings = Smaa; + /// A preset quality level for SMAA. /// /// Higher values are slower but result in a higher-quality image. @@ -339,8 +343,8 @@ impl Plugin for SmaaPlugin { .resource_mut::>() .insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); - app.add_plugins(ExtractComponentPlugin::::default()) - .register_type::(); + app.add_plugins(ExtractComponentPlugin::::default()) + .register_type::(); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -614,13 +618,13 @@ fn prepare_smaa_pipelines( pipeline_cache: Res, mut specialized_render_pipelines: ResMut, smaa_pipelines: Res, - view_targets: Query<(Entity, &ExtractedView, &SmaaSettings)>, + view_targets: Query<(Entity, &ExtractedView, &Smaa)>, ) { - for (entity, view, settings) in &view_targets { + for (entity, view, smaa) in &view_targets { let edge_detection_pipeline_id = specialized_render_pipelines.edge_detection.specialize( &pipeline_cache, &smaa_pipelines.edge_detection, - settings.preset, + smaa.preset, ); let blending_weight_calculation_pipeline_id = specialized_render_pipelines @@ -628,7 +632,7 @@ fn prepare_smaa_pipelines( .specialize( &pipeline_cache, &smaa_pipelines.blending_weight_calculation, - settings.preset, + smaa.preset, ); let neighborhood_blending_pipeline_id = specialized_render_pipelines @@ -642,7 +646,7 @@ fn prepare_smaa_pipelines( } else { TextureFormat::bevy_default() }, - preset: settings.preset, + preset: smaa.preset, }, ); @@ -660,7 +664,7 @@ fn prepare_smaa_uniforms( mut commands: Commands, render_device: Res, render_queue: Res, - view_targets: Query<(Entity, &ExtractedView), With>, + view_targets: Query<(Entity, &ExtractedView), With>, mut smaa_info_buffer: ResMut, ) { smaa_info_buffer.clear(); @@ -691,7 +695,7 @@ fn prepare_smaa_textures( mut commands: Commands, render_device: Res, mut texture_cache: ResMut, - view_targets: Query<(Entity, &ExtractedCamera), (With, With)>, + view_targets: Query<(Entity, &ExtractedCamera), (With, With)>, ) { for (entity, camera) in &view_targets { let Some(texture_size) = camera.physical_target_size else { @@ -765,7 +769,7 @@ fn prepare_smaa_bind_groups( render_device: Res, smaa_pipelines: Res, images: Res>, - view_targets: Query<(Entity, &SmaaTextures), (With, With)>, + view_targets: Query<(Entity, &SmaaTextures), (With, With)>, ) { // Fetch the two lookup textures. These are bundled in this library. let (Some(search_texture), Some(area_texture)) = ( diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 4a78af178e..635b0a7dbe 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -8,13 +8,14 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, Handle}; use bevy_core::FrameCount; use bevy_ecs::{ - prelude::{Bundle, Component, Entity}, + prelude::{Bundle, Component, Entity, ReflectComponent}, query::{QueryItem, With}, schedule::IntoSystemConfigs, system::{Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; use bevy_math::vec2; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, @@ -40,14 +41,14 @@ const TAA_SHADER_HANDLE: Handle = Handle::weak_from_u128(656865235226276 /// Plugin for temporal anti-aliasing. /// -/// See [`TemporalAntiAliasSettings`] for more details. +/// See [`TemporalAntiAliasing`] for more details. pub struct TemporalAntiAliasPlugin; impl Plugin for TemporalAntiAliasPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl); - app.register_type::(); + app.register_type::(); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -88,7 +89,7 @@ impl Plugin for TemporalAntiAliasPlugin { /// Bundle to apply temporal anti-aliasing. #[derive(Bundle, Default, Clone)] pub struct TemporalAntiAliasBundle { - pub settings: TemporalAntiAliasSettings, + pub settings: TemporalAntiAliasing, pub jitter: TemporalJitter, pub depth_prepass: DepthPrepass, pub motion_vector_prepass: MotionVectorPrepass, @@ -136,7 +137,9 @@ pub struct TemporalAntiAliasBundle { /// /// If no [`MipBias`] component is attached to the camera, TAA will add a MipBias(-1.0) component. #[derive(Component, Reflect, Clone)] -pub struct TemporalAntiAliasSettings { +#[reflect(Component, Default)] +#[doc(alias = "Taa")] +pub struct TemporalAntiAliasing { /// Set to true to delete the saved temporal history (past frames). /// /// Useful for preventing ghosting when the history is no longer @@ -147,7 +150,10 @@ pub struct TemporalAntiAliasSettings { pub reset: bool, } -impl Default for TemporalAntiAliasSettings { +#[deprecated(since = "0.15.0", note = "Renamed to `TemporalAntiAliasing`")] +pub type TemporalAntiAliasSettings = TemporalAntiAliasing; + +impl Default for TemporalAntiAliasing { fn default() -> Self { Self { reset: true } } @@ -347,7 +353,7 @@ impl SpecializedRenderPipeline for TaaPipeline { fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut) { let mut cameras_3d = main_world - .query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasSettings), ( + .query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasing), ( With, With, With, @@ -367,10 +373,7 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut, - mut query: Query< - (Entity, &mut TemporalJitter, Option<&MipBias>), - With, - >, + mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With>, mut commands: Commands, ) { // Halton sequence (2, 3) - 0.5, skipping i = 0 @@ -407,7 +410,7 @@ fn prepare_taa_history_textures( mut texture_cache: ResMut, render_device: Res, frame_count: Res, - views: Query<(Entity, &ExtractedCamera, &ExtractedView), With>, + views: Query<(Entity, &ExtractedCamera, &ExtractedView), With>, ) { for (entity, camera, view) in &views { if let Some(physical_target_size) = camera.physical_target_size { @@ -461,7 +464,7 @@ fn prepare_taa_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<(Entity, &ExtractedView, &TemporalAntiAliasSettings)>, + views: Query<(Entity, &ExtractedView, &TemporalAntiAliasing)>, ) { for (entity, view, taa_settings) in &views { let mut pipeline_key = TaaPipelineKey { diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index f734bfcf6e..353690d165 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -2,6 +2,7 @@ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, Assets, Handle}; use bevy_ecs::prelude::*; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::extract_resource::{ExtractResource, ExtractResourcePlugin}; @@ -136,7 +137,7 @@ pub struct TonemappingPipeline { Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq, )] #[extract_component_filter(With)] -#[reflect(Component)] +#[reflect(Component, Debug, Hash, Default, PartialEq)] pub enum Tonemapping { /// Bypass tonemapping. None, @@ -391,7 +392,7 @@ pub fn prepare_view_tonemapping_pipelines( Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq, )] #[extract_component_filter(With)] -#[reflect(Component)] +#[reflect(Component, Debug, Hash, Default, PartialEq)] pub enum DebandDither { #[default] Disabled, diff --git a/crates/bevy_dev_tools/src/ci_testing/mod.rs b/crates/bevy_dev_tools/src/ci_testing/mod.rs index d6d5c6d4be..39b9bee6da 100644 --- a/crates/bevy_dev_tools/src/ci_testing/mod.rs +++ b/crates/bevy_dev_tools/src/ci_testing/mod.rs @@ -17,8 +17,9 @@ use std::time::Duration; /// (`ci_testing_config.ron` by default) and executes its specified actions. For a reference of the /// allowed configuration, see [`CiTestingConfig`]. /// -/// This plugin is included within `DefaultPlugins` and `MinimalPlugins` when the `bevy_ci_testing` -/// feature is enabled. It is recommended to only used this plugin during testing (manual or +/// This plugin is included within `DefaultPlugins`, `HeadlessPlugins` and `MinimalPlugins` +/// when the `bevy_ci_testing` feature is enabled. +/// It is recommended to only used this plugin during testing (manual or /// automatic), and disable it during regular development and for production builds. #[derive(Default)] pub struct CiTestingPlugin; diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index b6d1ed2bd7..9791da236c 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -5,12 +5,14 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy_ecs::{ + change_detection::DetectChangesMut, component::Component, query::With, schedule::{common_conditions::resource_changed, IntoSystemConfigs}, system::{Commands, Query, Res, Resource}, }; use bevy_hierarchy::{BuildChildren, ChildBuild}; +use bevy_render::view::Visibility; use bevy_text::{Font, Text, TextSection, TextStyle}; use bevy_ui::{ node_bundles::{NodeBundle, TextBundle}, @@ -47,7 +49,7 @@ impl Plugin for FpsOverlayPlugin { .add_systems( Update, ( - customize_text.run_if(resource_changed::), + (customize_text, toggle_display).run_if(resource_changed::), update_text, ), ); @@ -59,6 +61,8 @@ impl Plugin for FpsOverlayPlugin { pub struct FpsOverlayConfig { /// Configuration of text in the overlay. pub text_config: TextStyle, + /// Displays the FPS overlay if true. + pub enabled: bool, } impl Default for FpsOverlayConfig { @@ -69,6 +73,7 @@ impl Default for FpsOverlayConfig { font_size: 32.0, color: Color::WHITE, }, + enabled: true, } } } @@ -121,3 +126,15 @@ fn customize_text( } } } + +fn toggle_display( + overlay_config: Res, + mut query: Query<&mut Visibility, With>, +) { + for mut visibility in &mut query { + visibility.set_if_neq(match overlay_config.enabled { + true => Visibility::Visible, + false => Visibility::Hidden, + }); + } +} diff --git a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs index 6382cfa903..88af56210a 100644 --- a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs +++ b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs @@ -92,7 +92,7 @@ fn update_debug_camera( projection: OrthographicProjection { far: 1000.0, viewport_origin: Vec2::new(0.0, 0.0), - ..default() + ..OrthographicProjection::default_3d() }, camera: Camera { order: LAYOUT_DEBUG_CAMERA_ORDER, diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 173de89ad3..125054041a 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -34,6 +34,7 @@ serde = { version = "1", optional = true, default-features = false } thiserror = "1.0" nonmax = "0.5" arrayvec = { version = "0.7.4", optional = true } +smallvec = "1" [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index 2a2916905a..378e26df10 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -120,7 +120,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { ); let filter_impl = quote! { - impl #user_impl_generics #path::query::QueryFilter + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl #user_impl_generics #path::query::QueryFilter for #struct_name #user_ty_generics #user_where_clauses { const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 15e962291c..7df84b87c4 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -499,6 +499,16 @@ impl Archetype { self.components.len() } + /// Gets an iterator of all of the components in the archetype, along with + /// their archetype component ID. + pub(crate) fn components_with_archetype_component_id( + &self, + ) -> impl Iterator + '_ { + self.components + .iter() + .map(|(component_id, info)| (*component_id, info.archetype_component_id)) + } + /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] @@ -766,7 +776,7 @@ pub struct Archetypes { /// find the archetype id by the archetype's components by_components: HashMap, /// find all the archetypes that contain a component - by_component: ComponentIndex, + pub(crate) by_component: ComponentIndex, } /// Metadata about how a component is stored in an [`Archetype`]. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 8311040bb3..607e14145a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -517,33 +517,30 @@ impl BundleInfo { let component_id = *self.component_ids.get_unchecked(bundle_component); match storage_type { StorageType::Table => { - let column = - // SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that - // the target table contains the component. - unsafe { table.get_column_mut(component_id).debug_checked_unwrap() }; // 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 + // 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_change_detection")] - caller, - ); - } - (ComponentStatus::Existing, InsertMode::Replace) => { - column.replace( - table_row, - component_ptr, - change_tick, - #[cfg(feature = "track_change_detection")] - caller, - ); - } + (ComponentStatus::Added, _) => column.initialize( + table_row, + component_ptr, + change_tick, + #[cfg(feature = "track_change_detection")] + caller, + ), + (ComponentStatus::Existing, InsertMode::Replace) => column.replace( + table_row, + component_ptr, + change_tick, + #[cfg(feature = "track_change_detection")] + caller, + ), (ComponentStatus::Existing, InsertMode::Keep) => { - column.drop(component_ptr); + if let Some(drop_fn) = table.get_drop_for(component_id) { + drop_fn(component_ptr); + } } } } @@ -1282,7 +1279,7 @@ impl<'w> BundleSpawner<'w> { unsafe { &mut self.world.world_mut().entities } } - /// # Safety: + /// # Safety /// - `Self` must be dropped after running this function as it may invalidate internal pointers. #[inline] pub(crate) unsafe fn flush_commands(&mut self) { @@ -1347,11 +1344,13 @@ impl Bundles { } /// # Safety - /// A `BundleInfo` with the given `BundleId` must have been initialized for this instance of `Bundles`. + /// A [`BundleInfo`] with the given [`BundleId`] must have been initialized for this instance of `Bundles`. pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo { self.bundle_infos.get_unchecked(id.0) } + /// # Safety + /// This [`BundleId`] must have been initialized with a single [`Component`] (via [`init_component_info`](Self::init_dynamic_info)) pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType { *self .dynamic_component_storages @@ -1359,6 +1358,8 @@ impl Bundles { .debug_checked_unwrap() } + /// # Safety + /// This [`BundleId`] must have been initialized with multiple [`Component`]s (via [`init_dynamic_info`](Self::init_dynamic_info)) pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec { self.dynamic_bundle_storages .get_mut(&id) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 53e64e7e2c..910bd9fc10 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -738,9 +738,9 @@ impl Debug for ComponentDescriptor { } impl ComponentDescriptor { - /// # SAFETY + /// # Safety /// - /// `x` must points to a valid value of type `T`. + /// `x` must point to a valid value of type `T`. unsafe fn drop_ptr(x: OwningPtr<'_>) { // SAFETY: Contract is required to be upheld by the caller. unsafe { diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 93b1e78ffa..24a6f3a672 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -553,12 +553,14 @@ impl Entities { // Use one atomic subtract to grab a range of new IDs. The range might be // entirely nonnegative, meaning all IDs come from the freelist, or entirely // negative, meaning they are all new IDs to allocate, or a mix of both. - let range_end = self - .free_cursor - // Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics - // and use AtomicIsize instead (see note on `IdCursor`). - .fetch_sub(IdCursor::try_from(count).unwrap(), Ordering::Relaxed); - let range_start = range_end - IdCursor::try_from(count).unwrap(); + let range_end = self.free_cursor.fetch_sub( + IdCursor::try_from(count) + .expect("64-bit atomic operations are not supported on this platform."), + Ordering::Relaxed, + ); + let range_start = range_end + - IdCursor::try_from(count) + .expect("64-bit atomic operations are not supported on this platform."); let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize; @@ -745,9 +747,9 @@ impl Entities { self.verify_flushed(); let freelist_size = *self.free_cursor.get_mut(); - // Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics - // and use AtomicIsize instead (see note on `IdCursor`). - let shortfall = IdCursor::try_from(additional).unwrap() - freelist_size; + let shortfall = IdCursor::try_from(additional) + .expect("64-bit atomic operations are not supported on this platform.") + - freelist_size; if shortfall > 0 { self.meta.reserve(shortfall as usize); } @@ -1004,7 +1006,6 @@ impl EntityLocation { #[cfg(test)] mod tests { use super::*; - use std::mem::size_of; #[test] fn entity_niche_optimization() { diff --git a/crates/bevy_ecs/src/event/collections.rs b/crates/bevy_ecs/src/event/collections.rs index 3f5417d500..ea203bdc46 100644 --- a/crates/bevy_ecs/src/event/collections.rs +++ b/crates/bevy_ecs/src/event/collections.rs @@ -1,10 +1,12 @@ use crate as bevy_ecs; +#[cfg(feature = "bevy_reflect")] +use bevy_ecs::reflect::ReflectResource; use bevy_ecs::{ event::{Event, EventCursor, EventId, EventInstance}, system::Resource, }; #[cfg(feature = "bevy_reflect")] -use bevy_reflect::Reflect; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_utils::detailed_trace; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; @@ -43,7 +45,7 @@ use std::ops::{Deref, DerefMut}; /// /// // setup /// let mut events = Events::::default(); -/// let mut reader = events.get_reader(); +/// let mut cursor = events.get_cursor(); /// /// // run this once per update/frame /// events.update(); @@ -52,12 +54,12 @@ use std::ops::{Deref, DerefMut}; /// events.send(MyEvent { value: 1 }); /// /// // somewhere else: read the events -/// for event in reader.read(&events) { +/// for event in cursor.read(&events) { /// assert_eq!(event.value, 1) /// } /// /// // events are only processed once per reader -/// assert_eq!(reader.read(&events).count(), 0); +/// assert_eq!(cursor.read(&events).count(), 0); /// ``` /// /// # Details @@ -85,7 +87,7 @@ use std::ops::{Deref, DerefMut}; /// [`EventWriter`]: super::EventWriter /// [`event_update_system`]: super::event_update_system #[derive(Debug, Resource)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))] pub struct Events { /// Holds the oldest still active events. /// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`. diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index 99f31881b2..11ad971244 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -51,7 +51,7 @@ use bevy_ecs::{ /// // /// // NOTE: the event won't actually be sent until commands get applied during /// // apply_deferred. -/// commands.add(|w: &mut World| { +/// commands.queue(|w: &mut World| { /// w.send_event(MyEvent); /// }); /// } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index cbaf638e29..45d007b930 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -623,7 +623,6 @@ mod tests { .collect::>(), HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))]) ); - assert_eq!(world.entity_mut(e1).take::(), Some(A(1))); assert_eq!( world diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c6ace80ca5..3919321df2 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -7,11 +7,12 @@ mod trigger_event; pub use runner::*; pub use trigger_event::*; +use crate::entity::EntityHashMap; use crate::observer::entity_observer::ObservedBy; use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; use bevy_ptr::Ptr; -use bevy_utils::{EntityHashMap, HashMap}; +use bevy_utils::HashMap; use std::{fmt::Debug, marker::PhantomData}; /// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the @@ -55,11 +56,37 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> { Ptr::from(&self.event) } - /// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`]. + /// Returns the [`Entity`] that triggered the observer, could be [`Entity::PLACEHOLDER`]. pub fn entity(&self) -> Entity { self.trigger.entity } + /// Returns the [`Entity`] that observed the triggered event. + /// This allows you to despawn the observer, ceasing observation. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_ecs::prelude::{Commands, Trigger}; + /// # + /// # struct MyEvent { + /// # done: bool, + /// # } + /// # + /// /// Handle `MyEvent` and if it is done, stop observation. + /// fn my_observer(trigger: Trigger, mut commands: Commands) { + /// if trigger.event().done { + /// commands.entity(trigger.observer()).despawn(); + /// return; + /// } + /// + /// // ... + /// } + /// ``` + pub fn observer(&self) -> Entity { + self.trigger.observer + } + /// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities. /// /// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events @@ -152,7 +179,7 @@ pub struct ObserverTrigger { } // Map between an observer entity and its runner -type ObserverMap = EntityHashMap; +type ObserverMap = EntityHashMap; /// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component. #[derive(Default, Debug)] @@ -160,7 +187,7 @@ pub struct CachedComponentObservers { // Observers listening to triggers targeting this component map: ObserverMap, // Observers listening to triggers targeting this component on a specific entity - entity_map: EntityHashMap, + entity_map: EntityHashMap, } /// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger. @@ -171,7 +198,7 @@ pub struct CachedObservers { // Observers listening for this trigger fired at a specific component component_observers: HashMap, // Observers listening for this trigger fired at a specific entity - entity_observers: EntityHashMap, + entity_observers: EntityHashMap, } /// Metadata for observers. Stores a cache mapping trigger ids to the registered observers. @@ -429,14 +456,17 @@ impl World { if observers.map.is_empty() && observers.entity_map.is_empty() { cache.component_observers.remove(component); if let Some(flag) = Observers::is_archetype_cached(event_type) { - for archetype in &mut archetypes.archetypes { - if archetype.contains(*component) { - let no_longer_observed = archetype - .components() - .all(|id| !cache.component_observers.contains_key(&id)); + if let Some(by_component) = archetypes.by_component.get(component) { + for archetype in by_component.keys() { + let archetype = &mut archetypes.archetypes[archetype.index()]; + if archetype.contains(*component) { + let no_longer_observed = archetype + .components() + .all(|id| !cache.component_observers.contains_key(&id)); - if no_longer_observed { - archetype.flags.set(flag, false); + if no_longer_observed { + archetype.flags.set(flag, false); + } } } } @@ -450,6 +480,8 @@ impl World { #[cfg(test)] mod tests { + use std::vec; + use bevy_ptr::OwningPtr; use crate as bevy_ecs; @@ -476,13 +508,12 @@ mod tests { struct EventA; #[derive(Resource, Default)] - struct R(usize); + struct Order(Vec<&'static str>); - impl R { + impl Order { #[track_caller] - fn assert_order(&mut self, count: usize) { - assert_eq!(count, self.0); - self.0 += 1; + fn observed(&mut self, name: &'static str) { + self.0.push(name); } } @@ -507,61 +538,72 @@ mod tests { #[test] fn observer_order_spawn_despawn() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(2)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(3)); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("insert")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("replace")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("remove")); let entity = world.spawn(A).id(); world.despawn(entity); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["add", "insert", "replace", "remove"], + world.resource::().0 + ); } #[test] fn observer_order_insert_remove() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(2)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(3)); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("insert")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("replace")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("remove")); let mut entity = world.spawn_empty(); entity.insert(A); entity.remove::(); entity.flush(); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["add", "insert", "replace", "remove"], + world.resource::().0 + ); } #[test] fn observer_order_insert_remove_sparse() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(2)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(3)); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("insert")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("replace")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("remove")); let mut entity = world.spawn_empty(); entity.insert(S); entity.remove::(); entity.flush(); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["add", "insert", "replace", "remove"], + world.resource::().0 + ); } #[test] fn observer_order_replace() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let entity = world.spawn(A).id(); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(0)); - world.observe(|_: Trigger, mut res: ResMut| res.assert_order(1)); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("insert")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("replace")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("remove")); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut // and therefore does not automatically flush. @@ -570,53 +612,56 @@ mod tests { let mut entity = world.entity_mut(entity); entity.insert(A); entity.flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["replace", "insert"], world.resource::().0); } #[test] fn observer_order_recursive() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); world.observe( - |obs: Trigger, mut res: ResMut, mut commands: Commands| { - res.assert_order(0); + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.observed("add_a"); commands.entity(obs.entity()).insert(B); }, ); world.observe( - |obs: Trigger, mut res: ResMut, mut commands: Commands| { - res.assert_order(2); + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.observed("remove_a"); commands.entity(obs.entity()).remove::(); }, ); world.observe( - |obs: Trigger, mut res: ResMut, mut commands: Commands| { - res.assert_order(1); + |obs: Trigger, mut res: ResMut, mut commands: Commands| { + res.observed("add_b"); commands.entity(obs.entity()).remove::(); }, ); - world.observe(|_: Trigger, mut res: ResMut| { - res.assert_order(3); + world.observe(|_: Trigger, mut res: ResMut| { + res.observed("remove_b"); }); let entity = world.spawn(A).flush(); let entity = world.get_entity(entity).unwrap(); assert!(!entity.contains::()); assert!(!entity.contains::()); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["add_a", "add_b", "remove_a", "remove_b"], + world.resource::().0 + ); } #[test] fn observer_multiple_listeners() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add_1")); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add_2")); world.spawn(A).flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["add_1", "add_2"], world.resource::().0); // Our A entity plus our two observers assert_eq!(world.entities().len(), 3); } @@ -624,40 +669,44 @@ mod tests { #[test] fn observer_multiple_events() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let on_remove = world.init_component::(); world.spawn( // SAFETY: OnAdd and OnRemove are both unit types, so this is safe unsafe { - Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) - .with_event(on_remove) + Observer::new(|_: Trigger, mut res: ResMut| { + res.observed("add/remove"); + }) + .with_event(on_remove) }, ); let entity = world.spawn(A).id(); world.despawn(entity); - assert_eq!(2, world.resource::().0); + assert_eq!( + vec!["add/remove", "add/remove"], + world.resource::().0 + ); } #[test] fn observer_multiple_components() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); world.init_component::(); world.init_component::(); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add_ab")); let entity = world.spawn(A).id(); world.entity_mut(entity).insert(B); world.flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["add_ab", "add_ab"], world.resource::().0); } #[test] fn observer_despawn() { let mut world = World::new(); - world.init_resource::(); let observer = world .observe(|_: Trigger| panic!("Observer triggered after being despawned.")) @@ -670,11 +719,11 @@ mod tests { #[test] fn observer_despawn_archetype_flags() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let entity = world.spawn((A, B)).flush(); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.observed("remove_a")); let observer = world .observe(|_: Trigger| panic!("Observer triggered after being despawned.")) @@ -683,31 +732,31 @@ mod tests { world.despawn(entity); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["remove_a"], world.resource::().0); } #[test] fn observer_multiple_matches() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.observed("add_ab")); world.spawn((A, B)).flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["add_ab"], world.resource::().0); } #[test] fn observer_no_target() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); world .spawn_empty() .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); - world.observe(move |obs: Trigger, mut res: ResMut| { + world.observe(move |obs: Trigger, mut res: ResMut| { assert_eq!(obs.entity(), Entity::PLACEHOLDER); - res.0 += 1; + res.observed("event_a"); }); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -715,24 +764,24 @@ mod tests { world.flush(); world.trigger(EventA); world.flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["event_a"], world.resource::().0); } #[test] fn observer_entity_routing() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); world .spawn_empty() .observe(|_: Trigger| panic!("Trigger routed to non-targeted entity.")); let entity = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("a_1")) .id(); - world.observe(move |obs: Trigger, mut res: ResMut| { + world.observe(move |obs: Trigger, mut res: ResMut| { assert_eq!(obs.entity(), entity); - res.0 += 1; + res.observed("a_2"); }); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -740,17 +789,17 @@ mod tests { world.flush(); world.trigger_targets(EventA, entity); world.flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["a_2", "a_1"], world.resource::().0); } #[test] fn observer_dynamic_component() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let component_id = world.init_component::(); world.spawn( - Observer::new(|_: Trigger, mut res: ResMut| res.0 += 1) + Observer::new(|_: Trigger, mut res: ResMut| res.observed("event_a")) .with_component(component_id), ); @@ -763,45 +812,45 @@ mod tests { world.trigger_targets(EventA, entity); world.flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["event_a"], world.resource::().0); } #[test] fn observer_dynamic_trigger() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let event_a = world.init_component::(); world.spawn(ObserverState { // SAFETY: we registered `event_a` above and it matches the type of TriggerA descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) }, runner: |mut world, _trigger, _ptr, _propagate| { - world.resource_mut::().0 += 1; + world.resource_mut::().observed("event_a"); }, ..Default::default() }); - world.commands().add( + world.commands().queue( // SAFETY: we registered `event_a` above and it matches the type of TriggerA unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) }, ); world.flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["event_a"], world.resource::().0); } #[test] fn observer_propagating() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("parent")) .id(); let child = world .spawn(Parent(parent)) - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("child")) .id(); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -809,22 +858,22 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, child); world.flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["child", "parent"], world.resource::().0); } #[test] fn observer_propagating_redundant_dispatch_same_entity() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("parent")) .id(); let child = world .spawn(Parent(parent)) - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("child")) .id(); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -832,22 +881,25 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, [child, child]); world.flush(); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["child", "parent", "child", "parent"], + world.resource::().0 + ); } #[test] fn observer_propagating_redundant_dispatch_parent_child() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("parent")) .id(); let child = world .spawn(Parent(parent)) - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("child")) .id(); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -855,24 +907,27 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, [child, parent]); world.flush(); - assert_eq!(3, world.resource::().0); + assert_eq!( + vec!["child", "parent", "parent"], + world.resource::().0 + ); } #[test] fn observer_propagating_halt() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("parent")) .id(); let child = world .spawn(Parent(parent)) .observe( - |mut trigger: Trigger, mut res: ResMut| { - res.0 += 1; + |mut trigger: Trigger, mut res: ResMut| { + res.observed("child"); trigger.propagate(false); }, ) @@ -883,30 +938,30 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, child); world.flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["child"], world.resource::().0); } #[test] fn observer_propagating_join() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("parent")) .id(); let child_a = world .spawn(Parent(parent)) - .observe(|_: Trigger, mut res: ResMut| { - res.0 += 1; + .observe(|_: Trigger, mut res: ResMut| { + res.observed("child_a"); }) .id(); let child_b = world .spawn(Parent(parent)) - .observe(|_: Trigger, mut res: ResMut| { - res.0 += 1; + .observe(|_: Trigger, mut res: ResMut| { + res.observed("child_b"); }) .id(); @@ -915,17 +970,20 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, [child_a, child_b]); world.flush(); - assert_eq!(4, world.resource::().0); + assert_eq!( + vec!["child_a", "parent", "child_b", "parent"], + world.resource::().0 + ); } #[test] fn observer_propagating_no_next() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let entity = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("event")) .id(); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -933,24 +991,26 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, entity); world.flush(); - assert_eq!(1, world.resource::().0); + assert_eq!(vec!["event"], world.resource::().0); } #[test] fn observer_propagating_parallel_propagation() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); let parent_a = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| { + res.observed("parent_a"); + }) .id(); let child_a = world .spawn(Parent(parent_a)) .observe( - |mut trigger: Trigger, mut res: ResMut| { - res.0 += 1; + |mut trigger: Trigger, mut res: ResMut| { + res.observed("child_a"); trigger.propagate(false); }, ) @@ -958,12 +1018,14 @@ mod tests { let parent_b = world .spawn_empty() - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| { + res.observed("parent_b"); + }) .id(); let child_b = world .spawn(Parent(parent_b)) - .observe(|_: Trigger, mut res: ResMut| res.0 += 1) + .observe(|_: Trigger, mut res: ResMut| res.observed("child_b")) .id(); // TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut @@ -971,15 +1033,18 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, [child_a, child_b]); world.flush(); - assert_eq!(3, world.resource::().0); + assert_eq!( + vec!["child_a", "child_b", "parent_b"], + world.resource::().0 + ); } #[test] fn observer_propagating_world() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); - world.observe(|_: Trigger, mut res: ResMut| res.0 += 1); + world.observe(|_: Trigger, mut res: ResMut| res.observed("event")); let grandparent = world.spawn_empty().id(); let parent = world.spawn(Parent(grandparent)).id(); @@ -990,18 +1055,18 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, child); world.flush(); - assert_eq!(3, world.resource::().0); + assert_eq!(vec!["event", "event", "event"], world.resource::().0); } #[test] fn observer_propagating_world_skipping() { let mut world = World::new(); - world.init_resource::(); + world.init_resource::(); world.observe( - |trigger: Trigger, query: Query<&A>, mut res: ResMut| { + |trigger: Trigger, query: Query<&A>, mut res: ResMut| { if query.get(trigger.entity()).is_ok() { - res.0 += 1; + res.observed("event"); } }, ); @@ -1015,6 +1080,6 @@ mod tests { world.flush(); world.trigger_targets(EventPropagating, child); world.flush(); - assert_eq!(2, world.resource::().0); + assert_eq!(vec!["event", "event"], world.resource::().0); } } diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 1706ead914..6ab582556d 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -1,5 +1,7 @@ +use std::any::Any; + use crate::{ - component::{ComponentHooks, ComponentId, StorageType}, + component::{ComponentHook, ComponentHooks, ComponentId, StorageType}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -63,7 +65,7 @@ impl Component for ObserverState { fn register_component_hooks(hooks: &mut ComponentHooks) { hooks.on_add(|mut world, entity, _| { - world.commands().add(move |world: &mut World| { + world.commands().queue(move |world: &mut World| { world.register_observer(entity); }); }); @@ -76,7 +78,7 @@ impl Component for ObserverState { .as_mut() .descriptor, ); - world.commands().add(move |world: &mut World| { + world.commands().queue(move |world: &mut World| { world.unregister_observer(entity, descriptor); }); }); @@ -257,22 +259,23 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: /// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`]. /// /// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and -/// serves as the "source of truth" of the observer. [`ObserverState`] can be used to filter for [`Observer`] [`Entity`]s, e.g. -/// [`With`]. +/// serves as the "source of truth" of the observer. /// /// [`SystemParam`]: crate::system::SystemParam -pub struct Observer { - system: BoxedObserverSystem, +pub struct Observer { + system: Box, descriptor: ObserverDescriptor, + hook_on_add: ComponentHook, } -impl Observer { +impl Observer { /// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered /// for _any_ entity (or no entity). - pub fn new(system: impl IntoObserverSystem) -> Self { + pub fn new>(system: I) -> Self { Self { system: Box::new(IntoObserverSystem::into_system(system)), descriptor: Default::default(), + hook_on_add: hook_on_add::, } } @@ -308,54 +311,20 @@ impl Observer { } } -impl Component for Observer { +impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|mut world, entity, _| { - world.commands().add(move |world: &mut World| { - let event_type = world.init_component::(); - let mut components = Vec::new(); - B::component_ids(&mut world.components, &mut world.storages, &mut |id| { - components.push(id); - }); - let mut descriptor = ObserverDescriptor { - events: vec![event_type], - components, - ..Default::default() - }; - - // Initialize System - let system: *mut dyn ObserverSystem = - if let Some(mut observe) = world.get_mut::(entity) { - descriptor.merge(&observe.descriptor); - &mut *observe.system - } else { - return; - }; - // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias - unsafe { - (*system).initialize(world); - } - - { - let mut entity = world.entity_mut(entity); - if let crate::world::Entry::Vacant(entry) = entity.entry::() { - entry.insert(ObserverState { - descriptor, - runner: observer_system_runner::, - ..Default::default() - }); - } - } - }); + hooks.on_add(|world, entity, _id| { + let Some(observe) = world.get::(entity) else { + return; + }; + let hook = observe.hook_on_add; + hook(world, entity, _id); }); } } -/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`]. -pub type BoxedObserverSystem = Box>; - -fn observer_system_runner( +fn observer_system_runner>( mut world: DeferredWorld, observer_trigger: ObserverTrigger, ptr: PtrMut, @@ -395,23 +364,75 @@ fn observer_system_runner( // This transmute is obviously not ideal, but it is safe. Ideally we can remove the // static constraint from ObserverSystem, but so far we have not found a way. let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) }; - // SAFETY: Observer was triggered so must have an `Observer` component. - let system = unsafe { - &mut observer_cell - .get_mut::>() - .debug_checked_unwrap() - .system + // SAFETY: + // - observer was triggered so must have an `Observer` component. + // - observer cannot be dropped or mutated until after the system pointer is already dropped. + let system: *mut dyn ObserverSystem = unsafe { + let mut observe = observer_cell.get_mut::().debug_checked_unwrap(); + let system = observe.system.downcast_mut::().unwrap(); + &mut *system }; - system.update_archetype_component_access(world); - // SAFETY: - // - `update_archetype_component_access` was just called + // - `update_archetype_component_access` is called first // - there are no outstanding references to world except a private component // - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld` // - system is the same type erased system from above unsafe { - system.run_unsafe(trigger, world); - system.queue_deferred(world.into_deferred()); + (*system).update_archetype_component_access(world); + (*system).run_unsafe(trigger, world); + (*system).queue_deferred(world.into_deferred()); } } + +/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`ComponentHooks::on_add`). +/// +/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters +/// erased. +/// +/// The type parameters of this function _must_ match those used to create the [`Observer`]. +/// As such, it is recommended to only use this function within the [`Observer::new`] method to +/// ensure type parameters match. +fn hook_on_add>( + mut world: DeferredWorld<'_>, + entity: Entity, + _: ComponentId, +) { + world.commands().queue(move |world: &mut World| { + let event_type = world.init_component::(); + let mut components = Vec::new(); + B::component_ids(&mut world.components, &mut world.storages, &mut |id| { + components.push(id); + }); + let mut descriptor = ObserverDescriptor { + events: vec![event_type], + components, + ..Default::default() + }; + + // Initialize System + let system: *mut dyn ObserverSystem = + if let Some(mut observe) = world.get_mut::(entity) { + descriptor.merge(&observe.descriptor); + let system = observe.system.downcast_mut::().unwrap(); + &mut *system + } else { + return; + }; + // SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias + unsafe { + (*system).initialize(world); + } + + { + let mut entity = world.entity_mut(entity); + if let crate::world::Entry::Vacant(entry) = entity.entry::() { + entry.insert(ObserverState { + descriptor, + runner: observer_system_runner::, + ..Default::default() + }); + } + } + }); +} diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 6769a754ca..0f5fb318af 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -49,20 +49,22 @@ impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> { /// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. #[derive(Eq, PartialEq)] pub struct Access { - /// All accessed components. + /// All accessed components, or forbidden components if + /// `Self::component_read_and_writes_inverted` is set. component_read_and_writes: FixedBitSet, - /// The exclusively-accessed components. + /// All exclusively-accessed components, or components that may not be + /// exclusively accessed if `Self::component_writes_inverted` is set. component_writes: FixedBitSet, /// All accessed resources. resource_read_and_writes: FixedBitSet, /// The exclusively-accessed resources. resource_writes: FixedBitSet, - /// Is `true` if this has access to all components. - /// (Note that this does not include `Resources`) - reads_all_components: bool, - /// Is `true` if this has mutable access to all components. - /// (Note that this does not include `Resources`) - writes_all_components: bool, + /// Is `true` if this component can read all components *except* those + /// present in `Self::component_read_and_writes`. + component_read_and_writes_inverted: bool, + /// Is `true` if this component can write to all components *except* those + /// present in `Self::component_writes`. + component_writes_inverted: bool, /// Is `true` if this has access to all resources. /// This field is a performance optimization for `&World` (also harder to mess up for soundness). reads_all_resources: bool, @@ -82,8 +84,8 @@ impl Clone for Access { component_writes: self.component_writes.clone(), resource_read_and_writes: self.resource_read_and_writes.clone(), resource_writes: self.resource_writes.clone(), - reads_all_components: self.reads_all_components, - writes_all_components: self.writes_all_components, + component_read_and_writes_inverted: self.component_read_and_writes_inverted, + component_writes_inverted: self.component_writes_inverted, reads_all_resources: self.reads_all_resources, writes_all_resources: self.writes_all_resources, archetypal: self.archetypal.clone(), @@ -98,8 +100,8 @@ impl Clone for Access { self.resource_read_and_writes .clone_from(&source.resource_read_and_writes); self.resource_writes.clone_from(&source.resource_writes); - self.reads_all_components = source.reads_all_components; - self.writes_all_components = source.writes_all_components; + self.component_read_and_writes_inverted = source.component_read_and_writes_inverted; + self.component_writes_inverted = source.component_writes_inverted; self.reads_all_resources = source.reads_all_resources; self.writes_all_resources = source.writes_all_resources; self.archetypal.clone_from(&source.archetypal); @@ -125,8 +127,11 @@ impl Debug for Access { "resource_writes", &FormattedBitSet::::new(&self.resource_writes), ) - .field("reads_all_components", &self.reads_all_components) - .field("writes_all_components", &self.writes_all_components) + .field( + "component_read_and_writes_inverted", + &self.component_read_and_writes_inverted, + ) + .field("component_writes_inverted", &self.component_writes_inverted) .field("reads_all_resources", &self.reads_all_resources) .field("writes_all_resources", &self.writes_all_resources) .field("archetypal", &FormattedBitSet::::new(&self.archetypal)) @@ -146,8 +151,8 @@ impl Access { Self { reads_all_resources: false, writes_all_resources: false, - reads_all_components: false, - writes_all_components: false, + component_read_and_writes_inverted: false, + component_writes_inverted: false, component_read_and_writes: FixedBitSet::new(), component_writes: FixedBitSet::new(), resource_read_and_writes: FixedBitSet::new(), @@ -157,18 +162,33 @@ impl Access { } } + fn add_component_sparse_set_index_read(&mut self, index: usize) { + if !self.component_read_and_writes_inverted { + self.component_read_and_writes.grow_and_insert(index); + } else if index < self.component_read_and_writes.len() { + self.component_read_and_writes.remove(index); + } + } + + fn add_component_sparse_set_index_write(&mut self, index: usize) { + if !self.component_writes_inverted { + self.component_writes.grow_and_insert(index); + } else if index < self.component_writes.len() { + self.component_writes.remove(index); + } + } + /// Adds access to the component given by `index`. pub fn add_component_read(&mut self, index: T) { - self.component_read_and_writes - .grow_and_insert(index.sparse_set_index()); + let sparse_set_index = index.sparse_set_index(); + self.add_component_sparse_set_index_read(sparse_set_index); } /// Adds exclusive access to the component given by `index`. pub fn add_component_write(&mut self, index: T) { - self.component_read_and_writes - .grow_and_insert(index.sparse_set_index()); - self.component_writes - .grow_and_insert(index.sparse_set_index()); + let sparse_set_index = index.sparse_set_index(); + self.add_component_sparse_set_index_read(sparse_set_index); + self.add_component_sparse_set_index_write(sparse_set_index); } /// Adds access to the resource given by `index`. @@ -185,6 +205,49 @@ impl Access { .grow_and_insert(index.sparse_set_index()); } + fn remove_component_sparse_set_index_read(&mut self, index: usize) { + if self.component_read_and_writes_inverted { + self.component_read_and_writes.grow_and_insert(index); + } else if index < self.component_read_and_writes.len() { + self.component_read_and_writes.remove(index); + } + } + + fn remove_component_sparse_set_index_write(&mut self, index: usize) { + if self.component_writes_inverted { + self.component_writes.grow_and_insert(index); + } else if index < self.component_writes.len() { + self.component_writes.remove(index); + } + } + + /// Removes both read and write access to the component given by `index`. + /// + /// Because this method corresponds to the set difference operator ∖, it can + /// create complicated logical formulas that you should verify correctness + /// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you + /// can't replace a call to `remove_component_read` followed by a call to + /// `extend` with a call to `extend` followed by a call to + /// `remove_component_read`. + pub fn remove_component_read(&mut self, index: T) { + let sparse_set_index = index.sparse_set_index(); + self.remove_component_sparse_set_index_write(sparse_set_index); + self.remove_component_sparse_set_index_read(sparse_set_index); + } + + /// Removes write access to the component given by `index`. + /// + /// Because this method corresponds to the set difference operator ∖, it can + /// create complicated logical formulas that you should verify correctness + /// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you + /// can't replace a call to `remove_component_write` followed by a call to + /// `extend` with a call to `extend` followed by a call to + /// `remove_component_write`. + pub fn remove_component_write(&mut self, index: T) { + let sparse_set_index = index.sparse_set_index(); + self.remove_component_sparse_set_index_write(sparse_set_index); + } + /// Adds an archetypal (indirect) access to the component given by `index`. /// /// This is for components whose values are not accessed (and thus will never cause conflicts), @@ -199,25 +262,25 @@ impl Access { /// Returns `true` if this can access the component given by `index`. pub fn has_component_read(&self, index: T) -> bool { - self.reads_all_components - || self + self.component_read_and_writes_inverted + ^ self .component_read_and_writes .contains(index.sparse_set_index()) } /// Returns `true` if this can access any component. pub fn has_any_component_read(&self) -> bool { - self.reads_all_components || !self.component_read_and_writes.is_clear() + self.component_read_and_writes_inverted || !self.component_read_and_writes.is_clear() } /// Returns `true` if this can exclusively access the component given by `index`. pub fn has_component_write(&self, index: T) -> bool { - self.writes_all_components || self.component_writes.contains(index.sparse_set_index()) + self.component_writes_inverted ^ self.component_writes.contains(index.sparse_set_index()) } /// Returns `true` if this accesses any component mutably. pub fn has_any_component_write(&self) -> bool { - self.writes_all_components || !self.component_writes.is_clear() + self.component_writes_inverted || !self.component_writes.is_clear() } /// Returns `true` if this can access the resource given by `index`. @@ -258,14 +321,16 @@ impl Access { /// Sets this as having access to all components (i.e. `EntityRef`). #[inline] pub fn read_all_components(&mut self) { - self.reads_all_components = true; + self.component_read_and_writes_inverted = true; + self.component_read_and_writes.clear(); } /// Sets this as having mutable access to all components (i.e. `EntityMut`). #[inline] pub fn write_all_components(&mut self) { - self.reads_all_components = true; - self.writes_all_components = true; + self.read_all_components(); + self.component_writes_inverted = true; + self.component_writes.clear(); } /// Sets this as having access to all resources (i.e. `&World`). @@ -298,13 +363,13 @@ impl Access { /// Returns `true` if this has access to all components (i.e. `EntityRef`). #[inline] pub fn has_read_all_components(&self) -> bool { - self.reads_all_components + self.component_read_and_writes_inverted && self.component_read_and_writes.is_clear() } /// Returns `true` if this has write access to all components (i.e. `EntityMut`). #[inline] pub fn has_write_all_components(&self) -> bool { - self.writes_all_components + self.component_writes_inverted && self.component_writes.is_clear() } /// Returns `true` if this has access to all resources (i.e. `EntityRef`). @@ -332,7 +397,7 @@ impl Access { /// Removes all writes. pub fn clear_writes(&mut self) { self.writes_all_resources = false; - self.writes_all_components = false; + self.component_writes_inverted = false; self.component_writes.clear(); self.resource_writes.clear(); } @@ -341,8 +406,8 @@ impl Access { pub fn clear(&mut self) { self.reads_all_resources = false; self.writes_all_resources = false; - self.reads_all_components = false; - self.writes_all_components = false; + self.component_read_and_writes_inverted = false; + self.component_writes_inverted = false; self.component_read_and_writes.clear(); self.component_writes.clear(); self.resource_read_and_writes.clear(); @@ -351,13 +416,72 @@ impl Access { /// Adds all access from `other`. pub fn extend(&mut self, other: &Access) { + let component_read_and_writes_inverted = + self.component_read_and_writes_inverted || other.component_read_and_writes_inverted; + let component_writes_inverted = + self.component_writes_inverted || other.component_writes_inverted; + + match ( + self.component_read_and_writes_inverted, + other.component_read_and_writes_inverted, + ) { + (true, true) => { + self.component_read_and_writes + .intersect_with(&other.component_read_and_writes); + } + (true, false) => { + self.component_read_and_writes + .difference_with(&other.component_read_and_writes); + } + (false, true) => { + // We have to grow here because the new bits are going to get flipped to 1. + self.component_read_and_writes.grow( + self.component_read_and_writes + .len() + .max(other.component_read_and_writes.len()), + ); + self.component_read_and_writes.toggle_range(..); + self.component_read_and_writes + .intersect_with(&other.component_read_and_writes); + } + (false, false) => { + self.component_read_and_writes + .union_with(&other.component_read_and_writes); + } + } + + match ( + self.component_writes_inverted, + other.component_writes_inverted, + ) { + (true, true) => { + self.component_writes + .intersect_with(&other.component_writes); + } + (true, false) => { + self.component_writes + .difference_with(&other.component_writes); + } + (false, true) => { + // We have to grow here because the new bits are going to get flipped to 1. + self.component_writes.grow( + self.component_writes + .len() + .max(other.component_writes.len()), + ); + self.component_writes.toggle_range(..); + self.component_writes + .intersect_with(&other.component_writes); + } + (false, false) => { + self.component_writes.union_with(&other.component_writes); + } + } + self.reads_all_resources = self.reads_all_resources || other.reads_all_resources; self.writes_all_resources = self.writes_all_resources || other.writes_all_resources; - self.reads_all_components = self.reads_all_components || other.reads_all_components; - self.writes_all_components = self.writes_all_components || other.writes_all_components; - self.component_read_and_writes - .union_with(&other.component_read_and_writes); - self.component_writes.union_with(&other.component_writes); + self.component_read_and_writes_inverted = component_read_and_writes_inverted; + self.component_writes_inverted = component_writes_inverted; self.resource_read_and_writes .union_with(&other.resource_read_and_writes); self.resource_writes.union_with(&other.resource_writes); @@ -369,27 +493,48 @@ impl Access { /// [`Access`] instances are incompatible if one can write /// an element that the other can read or write. pub fn is_components_compatible(&self, other: &Access) -> bool { - if self.writes_all_components { - return !other.has_any_component_read(); + // We have a conflict if we write and they read or write, or if they + // write and we read or write. + for ( + lhs_writes, + rhs_reads_and_writes, + lhs_writes_inverted, + rhs_reads_and_writes_inverted, + ) in [ + ( + &self.component_writes, + &other.component_read_and_writes, + self.component_writes_inverted, + other.component_read_and_writes_inverted, + ), + ( + &other.component_writes, + &self.component_read_and_writes, + other.component_writes_inverted, + self.component_read_and_writes_inverted, + ), + ] { + match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { + (true, true) => return false, + (false, true) => { + if !lhs_writes.is_subset(rhs_reads_and_writes) { + return false; + } + } + (true, false) => { + if !rhs_reads_and_writes.is_subset(lhs_writes) { + return false; + } + } + (false, false) => { + if !lhs_writes.is_disjoint(rhs_reads_and_writes) { + return false; + } + } + } } - if other.writes_all_components { - return !self.has_any_component_read(); - } - - if self.reads_all_components { - return !other.has_any_component_write(); - } - - if other.reads_all_components { - return !self.has_any_component_write(); - } - - self.component_writes - .is_disjoint(&other.component_read_and_writes) - && other - .component_writes - .is_disjoint(&self.component_read_and_writes) + true } /// Returns `true` if the access and `other` can be active at the same time, @@ -432,25 +577,48 @@ impl Access { /// Returns `true` if the set's component access is a subset of another, i.e. `other`'s component access /// contains at least all the values in `self`. pub fn is_subset_components(&self, other: &Access) -> bool { - if self.writes_all_components { - return other.writes_all_components; + for ( + our_components, + their_components, + our_components_inverted, + their_components_inverted, + ) in [ + ( + &self.component_read_and_writes, + &other.component_read_and_writes, + self.component_read_and_writes_inverted, + other.component_read_and_writes_inverted, + ), + ( + &self.component_writes, + &other.component_writes, + self.component_writes_inverted, + other.component_writes_inverted, + ), + ] { + match (our_components_inverted, their_components_inverted) { + (true, true) => { + if !their_components.is_subset(our_components) { + return false; + } + } + (true, false) => { + return false; + } + (false, true) => { + if !our_components.is_disjoint(their_components) { + return false; + } + } + (false, false) => { + if !our_components.is_subset(their_components) { + return false; + } + } + } } - if other.writes_all_components { - return true; - } - - if self.reads_all_components { - return other.reads_all_components; - } - - if other.reads_all_components { - return self.component_writes.is_subset(&other.component_writes); - } - - self.component_read_and_writes - .is_subset(&other.component_read_and_writes) - && self.component_writes.is_subset(&other.component_writes) + true } /// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access @@ -483,30 +651,52 @@ impl Access { self.is_subset_components(other) && self.is_subset_resources(other) } + fn get_component_conflicts(&self, other: &Access) -> AccessConflicts { + let mut conflicts = FixedBitSet::new(); + + // We have a conflict if we write and they read or write, or if they + // write and we read or write. + for ( + lhs_writes, + rhs_reads_and_writes, + lhs_writes_inverted, + rhs_reads_and_writes_inverted, + ) in [ + ( + &self.component_writes, + &other.component_read_and_writes, + self.component_writes_inverted, + other.component_read_and_writes_inverted, + ), + ( + &other.component_writes, + &self.component_read_and_writes, + other.component_writes_inverted, + self.component_read_and_writes_inverted, + ), + ] { + // There's no way that I can see to do this without a temporary. + // Neither CNF nor DNF allows us to avoid one. + let temp_conflicts: FixedBitSet = + match (lhs_writes_inverted, rhs_reads_and_writes_inverted) { + (true, true) => return AccessConflicts::All, + (false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(), + (true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(), + (false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(), + }; + conflicts.union_with(&temp_conflicts); + } + + AccessConflicts::Individual(conflicts) + } + /// Returns a vector of elements that the access and `other` cannot access at the same time. pub fn get_conflicts(&self, other: &Access) -> AccessConflicts { - let mut conflicts = FixedBitSet::new(); - if self.reads_all_components { - if other.writes_all_components { - return AccessConflicts::All; - } - conflicts.extend(other.component_writes.ones()); - } + let mut conflicts = match self.get_component_conflicts(other) { + AccessConflicts::All => return AccessConflicts::All, + AccessConflicts::Individual(conflicts) => conflicts, + }; - if other.reads_all_components { - if self.writes_all_components { - return AccessConflicts::All; - } - conflicts.extend(self.component_writes.ones()); - } - - if self.writes_all_components { - conflicts.extend(other.component_read_and_writes.ones()); - } - - if other.writes_all_components { - conflicts.extend(self.component_read_and_writes.ones()); - } if self.reads_all_resources { if other.writes_all_resources { return AccessConflicts::All; @@ -528,14 +718,6 @@ impl Access { conflicts.extend(self.resource_read_and_writes.ones()); } - conflicts.extend( - self.component_writes - .intersection(&other.component_read_and_writes), - ); - conflicts.extend( - self.component_read_and_writes - .intersection(&other.component_writes), - ); conflicts.extend( self.resource_writes .intersection(&other.resource_read_and_writes), @@ -547,25 +729,6 @@ impl Access { AccessConflicts::Individual(conflicts) } - /// Returns the indices of the components this has access to. - pub fn component_reads_and_writes(&self) -> impl Iterator + '_ { - self.component_read_and_writes - .ones() - .map(T::get_sparse_set_index) - } - - /// Returns the indices of the components this has non-exclusive access to. - pub fn component_reads(&self) -> impl Iterator + '_ { - self.component_read_and_writes - .difference(&self.component_writes) - .map(T::get_sparse_set_index) - } - - /// Returns the indices of the components this has exclusive access to. - pub fn component_writes(&self) -> impl Iterator + '_ { - self.component_writes.ones().map(T::get_sparse_set_index) - } - /// Returns the indices of the components that this has an archetypal access to. /// /// These are components whose values are not accessed (and thus will never cause conflicts), @@ -577,6 +740,40 @@ impl Access { pub fn archetypal(&self) -> impl Iterator + '_ { self.archetypal.ones().map(T::get_sparse_set_index) } + + /// Returns an iterator over the component IDs that this `Access` either + /// reads and writes or can't read or write. + /// + /// The returned flag specifies whether the list consists of the components + /// that the access *can* read or write (false) or whether the list consists + /// of the components that the access *can't* read or write (true). + /// + /// Because this method depends on internal implementation details of + /// `Access`, it's not recommended. Prefer to manage your own lists of + /// accessible components if your application needs to do that. + #[doc(hidden)] + #[deprecated] + pub fn component_reads_and_writes(&self) -> (impl Iterator + '_, bool) { + ( + self.component_read_and_writes + .ones() + .map(T::get_sparse_set_index), + self.component_read_and_writes_inverted, + ) + } + + /// Returns an iterator over the component IDs that this `Access` either + /// writes or can't write. + /// + /// The returned flag specifies whether the list consists of the components + /// that the access *can* write (false) or whether the list consists of the + /// components that the access *can't* write (true). + pub(crate) fn component_writes(&self) -> (impl Iterator + '_, bool) { + ( + self.component_writes.ones().map(T::get_sparse_set_index), + self.component_writes_inverted, + ) + } } /// An [`Access`] that has been filtered to include and exclude certain combinations of elements. diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 94a8af27c5..2f4217e711 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -79,10 +79,14 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { .map_or(false, |info| info.storage_type() == StorageType::Table) }; - self.access - .access() - .component_reads_and_writes() - .all(is_dense) + #[allow(deprecated)] + let (mut component_reads_and_writes, component_reads_and_writes_inverted) = + self.access.access().component_reads_and_writes(); + if component_reads_and_writes_inverted { + return false; + } + + component_reads_and_writes.all(is_dense) && self.access.access().archetypal().all(is_dense) && !self.access.access().has_read_all_components() && self.access.with_filters().all(is_dense) diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 8e522b61a2..311ddcc9aa 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,17 +1,19 @@ use crate::{ archetype::{Archetype, Archetypes}, + bundle::Bundle, change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut}, component::{Component, ComponentId, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, world::{ - unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut, - FilteredEntityRef, Mut, Ref, World, + unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, + FilteredEntityMut, FilteredEntityRef, Mut, Ref, World, }, }; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; use bevy_utils::all_tuples; +use smallvec::SmallVec; use std::{cell::UnsafeCell, marker::PhantomData}; /// Types that can be fetched from a [`World`] using a [`Query`]. @@ -626,27 +628,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> { unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, state: &Self::State, - archetype: &'w Archetype, + _: &'w Archetype, _table: &Table, ) { - let mut access = Access::default(); - state.access.component_reads().for_each(|id| { - if archetype.contains(id) { - access.add_component_read(id); - } - }); - fetch.1 = access; + fetch.1.clone_from(&state.access); } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { - let mut access = Access::default(); - state.access.component_reads().for_each(|id| { - if table.has_column(id) { - access.add_component_read(id); - } - }); - fetch.1 = access; + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { + fetch.1.clone_from(&state.access); } #[inline] @@ -733,37 +723,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> { unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, state: &Self::State, - archetype: &'w Archetype, + _: &'w Archetype, _table: &Table, ) { - let mut access = Access::default(); - state.access.component_reads().for_each(|id| { - if archetype.contains(id) { - access.add_component_read(id); - } - }); - state.access.component_writes().for_each(|id| { - if archetype.contains(id) { - access.add_component_write(id); - } - }); - fetch.1 = access; + fetch.1.clone_from(&state.access); } #[inline] - unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) { - let mut access = Access::default(); - state.access.component_reads().for_each(|id| { - if table.has_column(id) { - access.add_component_read(id); - } - }); - state.access.component_writes().for_each(|id| { - if table.has_column(id) { - access.add_component_write(id); - } - }); - fetch.1 = access; + unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) { + fetch.1.clone_from(&state.access); } #[inline] @@ -815,6 +783,201 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { type ReadOnly = FilteredEntityRef<'a>; } +/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B` +/// and populates `Access` values so that queries that conflict with this access +/// are rejected. +unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> +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 + } + + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _: &Self::State, + _: Tick, + _: Tick, + ) -> Self::Fetch<'w> { + world + } + + const IS_DENSE: bool = true; + + unsafe fn set_archetype<'w>( + _: &mut Self::Fetch<'w>, + _: &Self::State, + _: &'w Archetype, + _: &'w Table, + ) { + } + + 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, + ) { + let mut my_access = Access::new(); + my_access.read_all_components(); + for id in state { + my_access.remove_component_read(*id); + } + + let access = filtered_access.access_mut(); + assert!( + access.is_compatible(&my_access), + "`EntityRefExcept<{}>` conflicts with a previous access in this query.", + std::any::type_name::(), + ); + access.extend(&my_access); + } + + fn init_state(world: &mut World) -> Self::State { + Self::get_state(world.components()).unwrap() + } + + fn get_state(components: &Components) -> Option { + let mut ids = SmallVec::new(); + B::get_component_ids(components, &mut |maybe_id| { + if let Some(id) = maybe_id { + ids.push(id); + } + }); + Some(ids) + } + + fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool { + true + } +} + +/// SAFETY: `Self` is the same as `Self::ReadOnly`. +unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B> +where + B: Bundle, +{ + type ReadOnly = Self; +} + +/// SAFETY: `EntityRefExcept` enforces read-only access to its contained +/// components. +unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {} + +/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B` +/// and populates `Access` values so that queries that conflict with this access +/// are rejected. +unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> +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 + } + + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _: &Self::State, + _: Tick, + _: Tick, + ) -> Self::Fetch<'w> { + world + } + + const IS_DENSE: bool = true; + + unsafe fn set_archetype<'w>( + _: &mut Self::Fetch<'w>, + _: &Self::State, + _: &'w Archetype, + _: &'w Table, + ) { + } + + 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, + ) { + let mut my_access = Access::new(); + my_access.write_all_components(); + for id in state { + my_access.remove_component_read(*id); + } + + let access = filtered_access.access_mut(); + assert!( + access.is_compatible(&my_access), + "`EntityMutExcept<{}>` conflicts with a previous access in this query.", + std::any::type_name::() + ); + access.extend(&my_access); + } + + fn init_state(world: &mut World) -> Self::State { + Self::get_state(world.components()).unwrap() + } + + fn get_state(components: &Components) -> Option { + let mut ids = SmallVec::new(); + B::get_component_ids(components, &mut |maybe_id| { + if let Some(id) = maybe_id { + ids.push(id); + } + }); + Some(ids) + } + + fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool { + true + } +} + +/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that +/// `EntityMutExcept` provides. +unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B> +where + B: Bundle, +{ + type ReadOnly = EntityRefExcept<'a, B>; +} + /// SAFETY: /// `update_component_access` and `update_archetype_component_access` do nothing. /// This is sound because `fetch` does not access components. @@ -982,9 +1145,8 @@ unsafe impl WorldQuery for &T { ) { fetch.table_components = Some( table - .get_column(component_id) + .get_data_slice_for(component_id) .debug_checked_unwrap() - .get_data_slice() .into(), ); } @@ -1147,11 +1309,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { ) { let column = table.get_column(component_id).debug_checked_unwrap(); fetch.table_data = Some(( - column.get_data_slice().into(), - column.get_added_ticks_slice().into(), - column.get_changed_ticks_slice().into(), + 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_change_detection")] - column.get_changed_by_slice().into(), + column.get_changed_by_slice(table.entity_count()).into(), #[cfg(not(feature = "track_change_detection"))] (), )); @@ -1346,11 +1508,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ) { let column = table.get_column(component_id).debug_checked_unwrap(); fetch.table_data = Some(( - column.get_data_slice().into(), - column.get_added_ticks_slice().into(), - column.get_changed_ticks_slice().into(), + 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_change_detection")] - column.get_changed_by_slice().into(), + column.get_changed_by_slice(table.entity_count()).into(), #[cfg(not(feature = "track_change_detection"))] (), )); diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index affdaf2828..ee447ec99b 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -3,7 +3,7 @@ use crate::{ component::{Component, ComponentId, Components, StorageType, Tick}, entity::Entity, query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery}, - storage::{Column, ComponentSparseSet, Table, TableRow}, + storage::{ComponentSparseSet, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; @@ -70,12 +70,17 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// [`matches_component_set`]: Self::matches_component_set /// [`Query`]: crate::system::Query /// [`State`]: Self::State +/// +/// # Safety +/// +/// The [`WorldQuery`] implementation must not take any mutable access. +/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData). #[diagnostic::on_unimplemented( message = "`{Self}` is not a valid `Query` filter", label = "invalid `Query` filter", note = "a `QueryFilter` typically uses a combination of `With` and `Without` statements" )] -pub trait QueryFilter: WorldQuery { +pub unsafe trait QueryFilter: WorldQuery { /// Returns true if (and only if) this Filter relies strictly on archetypes to limit which /// components are accessed by the Query. /// @@ -201,7 +206,8 @@ unsafe impl WorldQuery for With { } } -impl QueryFilter for With { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for With { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -311,7 +317,8 @@ unsafe impl WorldQuery for Without { } } -impl QueryFilter for Without { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for Without { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -490,7 +497,8 @@ macro_rules! impl_or_query_filter { } } - impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { + // 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,)*)> { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; #[inline(always)] @@ -512,7 +520,8 @@ macro_rules! impl_tuple_query_filter { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] $(#[$meta])* - impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; #[inline(always)] @@ -677,7 +686,9 @@ unsafe impl WorldQuery for Added { table: &'w Table, ) { fetch.table_ticks = Some( - Column::get_added_ticks_slice(table.get_column(component_id).debug_checked_unwrap()) + table + .get_added_ticks_slice_for(component_id) + .debug_checked_unwrap() .into(), ); } @@ -734,7 +745,8 @@ unsafe impl WorldQuery for Added { } } -impl QueryFilter for Added { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; #[inline(always)] unsafe fn filter_fetch( @@ -892,7 +904,9 @@ unsafe impl WorldQuery for Changed { table: &'w Table, ) { fetch.table_ticks = Some( - Column::get_changed_ticks_slice(table.get_column(component_id).debug_checked_unwrap()) + table + .get_changed_ticks_slice_for(component_id) + .debug_checked_unwrap() .into(), ); } @@ -949,7 +963,8 @@ unsafe impl WorldQuery for Changed { } } -impl QueryFilter for Changed { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Changed { const IS_ARCHETYPAL: bool = false; #[inline(always)] diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 90749bcee9..6522a1b237 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -146,7 +146,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> { let range = range.unwrap_or(0..table.entity_count()); accum = - // SAFETY: + // SAFETY: // - The fetched table matches both D and F // - caller ensures `range` is within `[0, table.entity_count)` // - The if block ensures that the query iteration is dense @@ -1290,7 +1290,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator>> } } - /// Safety: + /// # Safety /// All arguments must stem from the same valid `QueryManyIter`. /// /// The lifetime here is not restrictive enough for Fetch with &mut access, @@ -1578,7 +1578,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter< } } - /// Safety: + /// # Safety /// The lifetime here is not restrictive enough for Fetch with &mut access, /// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple /// references to the same component, leading to unique reference aliasing. @@ -1733,6 +1733,9 @@ impl Clone for QueryIterationCursor<'_, '_, D, F> } impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { + /// # Safety + /// - `world` must have permission to access any of the components registered in `query_state`. + /// - `world` must be the same one used to initialize `query_state`. unsafe fn init_empty( world: UnsafeWorldCell<'w>, query_state: &'s QueryState, @@ -1781,25 +1784,41 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> { } } - /// retrieve item returned from most recent `next` call again. + /// Retrieve item returned from most recent `next` call again. + /// + /// # Safety + /// The result of `next` and any previous calls to `peek_last` with this row must have been + /// dropped to prevent aliasing mutable references. #[inline] unsafe fn peek_last(&mut self) -> Option> { if self.current_row > 0 { let index = self.current_row - 1; if self.is_dense { - let entity = self.table_entities.get_unchecked(index); - Some(D::fetch( - &mut self.fetch, - *entity, - TableRow::from_usize(index), - )) + // SAFETY: This must have been called previously in `next` as `current_row > 0` + let entity = unsafe { self.table_entities.get_unchecked(index) }; + // SAFETY: + // - `set_table` must have been called previously either in `next` or before it. + // - `*entity` and `index` are in the current table. + unsafe { + Some(D::fetch( + &mut self.fetch, + *entity, + TableRow::from_usize(index), + )) + } } else { - let archetype_entity = self.archetype_entities.get_unchecked(index); - Some(D::fetch( - &mut self.fetch, - archetype_entity.id(), - archetype_entity.table_row(), - )) + // SAFETY: This must have been called previously in `next` as `current_row > 0` + let archetype_entity = unsafe { self.archetype_entities.get_unchecked(index) }; + // SAFETY: + // - `set_archetype` must have been called previously either in `next` or before it. + // - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype. + unsafe { + Some(D::fetch( + &mut self.fetch, + archetype_entity.id(), + archetype_entity.table_row(), + )) + } } } else { None @@ -2083,7 +2102,7 @@ mod tests { let mut query = world.query::<&Sparse>(); let mut iter = query.iter(&world); println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, @@ -2091,7 +2110,7 @@ mod tests { ); _ = iter.next(); println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, @@ -2108,7 +2127,7 @@ mod tests { let mut query = world.query::<(&A, &Sparse)>(); let mut iter = query.iter(&world); println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, @@ -2116,7 +2135,7 @@ mod tests { ); _ = iter.next(); println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, @@ -2136,7 +2155,7 @@ mod tests { let mut query = world.query::<(&A, &Sparse)>(); let mut iter = query.iter(&world); println!( - "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, @@ -2145,7 +2164,7 @@ mod tests { assert!(iter.cursor.table_entities.len() | iter.cursor.archetype_entities.len() == 0); _ = iter.next(); println!( - "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", + "after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}", iter.cursor.archetype_entities.len(), iter.cursor.table_entities.len(), iter.cursor.current_len, diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index f1719d8ba5..3102a37d14 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -53,6 +53,40 @@ impl DebugCheckedUnwrap for Option { } } +// These two impls are explicitly split to ensure that the unreachable! macro +// does not cause inlining to fail when compiling in release mode. +#[cfg(debug_assertions)] +impl DebugCheckedUnwrap for Result { + type Item = T; + + #[inline(always)] + #[track_caller] + unsafe fn debug_checked_unwrap(self) -> Self::Item { + if let Ok(inner) = self { + inner + } else { + unreachable!() + } + } +} + +// These two impls are explicitly split to ensure that the unreachable! macro +// does not cause inlining to fail when compiling in release mode. +#[cfg(not(debug_assertions))] +impl DebugCheckedUnwrap for Result { + type Item = T; + + #[inline(always)] + #[track_caller] + unsafe fn debug_checked_unwrap(self) -> Self::Item { + if let Ok(inner) = self { + inner + } else { + std::hint::unreachable_unchecked() + } + } +} + #[cfg(not(debug_assertions))] impl DebugCheckedUnwrap for Option { type Item = T; @@ -69,13 +103,12 @@ impl DebugCheckedUnwrap for Option { #[cfg(test)] mod tests { - use bevy_ecs_macros::{QueryData, QueryFilter}; - use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without}; use crate::query::{ArchetypeFilter, Has, QueryCombinationIter, ReadOnlyQueryData}; use crate::schedule::{IntoSystemConfigs, Schedule}; use crate::system::{IntoSystem, Query, System, SystemState}; use crate::{self as bevy_ecs, component::Component, world::World}; + use bevy_ecs_macros::{QueryData, QueryFilter}; use std::any::type_name; use std::collections::HashSet; use std::fmt::Debug; diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 93d7155149..6ac740255c 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -123,7 +123,7 @@ impl QueryState { /// /// Consider using `as_readonly` or `as_nop` instead which are safe functions. /// - /// # SAFETY + /// # Safety /// /// `NewD` must have a subset of the access that `D` does and match the exact same archetypes/tables /// `NewF` must have a subset of the access that `F` does and match the exact same archetypes/tables @@ -508,22 +508,46 @@ impl QueryState { archetype: &Archetype, access: &mut Access, ) { - self.component_access - .access - .component_reads() - .for_each(|id| { + // As a fast path, we can iterate directly over the components involved + // if the `access` isn't inverted. + #[allow(deprecated)] + let (component_reads_and_writes, component_reads_and_writes_inverted) = + self.component_access.access.component_reads_and_writes(); + let (component_writes, component_writes_inverted) = + self.component_access.access.component_writes(); + + if !component_reads_and_writes_inverted && !component_writes_inverted { + component_reads_and_writes.for_each(|id| { if let Some(id) = archetype.get_archetype_component_id(id) { access.add_component_read(id); } }); - self.component_access - .access - .component_writes() - .for_each(|id| { + component_writes.for_each(|id| { if let Some(id) = archetype.get_archetype_component_id(id) { access.add_component_write(id); } }); + return; + } + + for (component_id, archetype_component_id) in + archetype.components_with_archetype_component_id() + { + if self + .component_access + .access + .has_component_read(component_id) + { + access.add_component_read(archetype_component_id); + } + if self + .component_access + .access + .has_component_write(component_id) + { + access.add_component_write(archetype_component_id); + } + } } /// Use this to transform a [`QueryState`] into a more generic [`QueryState`]. @@ -1174,14 +1198,11 @@ impl QueryState { /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many<'w, 's, EntityList: IntoIterator>( + pub fn iter_many<'w, 's, EntityList: IntoIterator>>( &'s mut self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { self.update_archetypes(world); // SAFETY: query is read only unsafe { @@ -1209,14 +1230,11 @@ impl QueryState { /// - [`iter_many`](Self::iter_many) to update archetypes. /// - [`iter_manual`](Self::iter_manual) to iterate over all query items. #[inline] - pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>>( &'s self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { self.validate_world(world.id()); // SAFETY: query is read only, world id is validated unsafe { @@ -1234,14 +1252,11 @@ impl QueryState { /// Items are returned in the order of the list of entities. /// Entities that don't match the query are skipped. #[inline] - pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>>( &'s mut self, world: &'w mut World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> 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(); @@ -1334,7 +1349,7 @@ impl QueryState { /// 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: IntoIterator>( + pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList>( &'s self, entities: EntityList, world: UnsafeWorldCell<'w>, @@ -1342,7 +1357,7 @@ impl QueryState { this_run: Tick, ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> where - EntityList::Item: Borrow, + EntityList: IntoIterator>, { QueryManyIter::new(world, self, entities, last_run, this_run) } diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index a48d1299bd..52e6800a49 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -2,23 +2,28 @@ use crate::prelude::Mut; use crate::reflect::AppTypeRegistry; use crate::system::{EntityCommands, Resource}; use crate::world::Command; -use crate::{entity::Entity, reflect::ReflectComponent, world::World}; +use crate::{ + entity::Entity, + reflect::{ReflectBundle, ReflectComponent}, + world::World, +}; use bevy_reflect::{PartialReflect, TypeRegistry}; use std::borrow::Cow; use std::marker::PhantomData; /// An extension trait for [`EntityCommands`] for reflection related functions pub trait ReflectCommandExt { - /// Adds the given boxed reflect component to the entity using the reflection data in + /// Adds the given boxed reflect component or bundle to the entity using the reflection data in /// [`AppTypeRegistry`]. /// - /// This will overwrite any previous component of the same type. + /// This will overwrite any previous component(s) of the same type. /// /// # Panics /// /// - If the entity doesn't exist. - /// - If [`AppTypeRegistry`] does not have the reflection data for the given [`Component`](crate::component::Component). - /// - If the component data is invalid. See [`PartialReflect::apply`] for further details. + /// - If [`AppTypeRegistry`] does not have the reflection data for the given + /// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle). + /// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details. /// - If [`AppTypeRegistry`] is not present in the [`World`]. /// /// # Note @@ -34,12 +39,12 @@ pub trait ReflectCommandExt { /// // or write to the TypeRegistry directly to register all your components /// /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::reflect::ReflectCommandExt; + /// # use bevy_ecs::reflect::{ReflectCommandExt, ReflectBundle}; /// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry}; /// // A resource that can hold any component that implements reflect as a boxed reflect component /// #[derive(Resource)] - /// struct Prefab{ - /// component: Box, + /// struct Prefab { + /// data: Box, /// } /// #[derive(Component, Reflect, Default)] /// #[reflect(Component)] @@ -49,6 +54,13 @@ pub trait ReflectCommandExt { /// #[reflect(Component)] /// struct ComponentB(String); /// + /// #[derive(Bundle, Reflect, Default)] + /// #[reflect(Bundle)] + /// struct BundleA { + /// a: ComponentA, + /// b: ComponentB, + /// } + /// /// fn insert_reflect_component( /// mut commands: Commands, /// mut prefab: ResMut @@ -56,16 +68,23 @@ pub trait ReflectCommandExt { /// // Create a set of new boxed reflect components to use /// let boxed_reflect_component_a: Box = Box::new(ComponentA(916)); /// let boxed_reflect_component_b: Box = Box::new(ComponentB("NineSixteen".to_string())); + /// let boxed_reflect_bundle_a: Box = Box::new(BundleA { + /// a: ComponentA(24), + /// b: ComponentB("Twenty-Four".to_string()), + /// }); /// /// // You can overwrite the component in the resource with either ComponentA or ComponentB - /// prefab.component = boxed_reflect_component_a; - /// prefab.component = boxed_reflect_component_b; - /// - /// // No matter which component is in the resource and without knowing the exact type, you can - /// // use the insert_reflect entity command to insert that component into an entity. + /// prefab.data = boxed_reflect_component_a; + /// prefab.data = boxed_reflect_component_b; + /// + /// // Or even with BundleA + /// prefab.data = boxed_reflect_bundle_a; + /// + /// // No matter which component or bundle is in the resource and without knowing the exact type, you can + /// // use the insert_reflect entity command to insert that component/bundle into an entity. /// commands /// .spawn_empty() - /// .insert_reflect(prefab.component.clone_value()); + /// .insert_reflect(prefab.data.clone_value()); /// } /// /// ``` @@ -86,10 +105,15 @@ pub trait ReflectCommandExt { component: Box, ) -> &mut Self; - /// Removes from the entity the component with the given type name registered in [`AppTypeRegistry`]. + /// Removes from the entity the component or bundle with the given type name registered in [`AppTypeRegistry`]. /// - /// Does nothing if the entity does not have a component of the same type, if [`AppTypeRegistry`] - /// does not contain the reflection data for the given component, or if the entity does not exist. + /// If the type is a bundle, it will remove any components in that bundle regardless if the entity + /// contains all the components. + /// + /// Does nothing if the type is a component and the entity does not have a component of the same type, + /// if the type is a bundle and the entity does not contain any of the components in the bundle, + /// if [`AppTypeRegistry`] does not contain the reflection data for the given component, + /// or if the entity does not exist. /// /// # Note /// @@ -99,19 +123,19 @@ pub trait ReflectCommandExt { /// # Example /// /// ``` - /// // Note that you need to register the component type in the AppTypeRegistry prior to using + /// // Note that you need to register the component/bundle type in the AppTypeRegistry prior to using /// // reflection. You can use the helpers on the App with `app.register_type::()` - /// // or write to the TypeRegistry directly to register all your components + /// // or write to the TypeRegistry directly to register all your components and bundles /// /// # use bevy_ecs::prelude::*; - /// # use bevy_ecs::reflect::ReflectCommandExt; + /// # use bevy_ecs::reflect::{ReflectCommandExt, ReflectBundle}; /// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry}; /// - /// // A resource that can hold any component that implements reflect as a boxed reflect component + /// // A resource that can hold any component or bundle that implements reflect as a boxed reflect /// #[derive(Resource)] /// struct Prefab{ /// entity: Entity, - /// component: Box, + /// data: Box, /// } /// #[derive(Component, Reflect, Default)] /// #[reflect(Component)] @@ -119,16 +143,23 @@ pub trait ReflectCommandExt { /// #[derive(Component, Reflect, Default)] /// #[reflect(Component)] /// struct ComponentB(String); + /// #[derive(Bundle, Reflect, Default)] + /// #[reflect(Bundle)] + /// struct BundleA { + /// a: ComponentA, + /// b: ComponentB, + /// } /// /// fn remove_reflect_component( /// mut commands: Commands, /// prefab: Res /// ) { - /// // Prefab can hold any boxed reflect component. In this case either - /// // ComponentA or ComponentB. No matter which component is in the resource though, - /// // we can attempt to remove any component of that same type from an entity. + /// // Prefab can hold any boxed reflect component or bundle. In this case either + /// // ComponentA, ComponentB, or BundleA. No matter which component or bundle is in the resource though, + /// // we can attempt to remove any component (or set of components in the case of a bundle) + /// // of that same type from an entity. /// commands.entity(prefab.entity) - /// .remove_reflect(prefab.component.reflect_type_path().to_owned()); + /// .remove_reflect(prefab.data.reflect_type_path().to_owned()); /// } /// /// ``` @@ -143,7 +174,7 @@ pub trait ReflectCommandExt { impl ReflectCommandExt for EntityCommands<'_> { fn insert_reflect(&mut self, component: Box) -> &mut Self { - self.commands.add(InsertReflect { + self.commands.queue(InsertReflect { entity: self.entity, component, }); @@ -154,7 +185,7 @@ impl ReflectCommandExt for EntityCommands<'_> { &mut self, component: Box, ) -> &mut Self { - self.commands.add(InsertReflectWithRegistry:: { + self.commands.queue(InsertReflectWithRegistry:: { entity: self.entity, _t: PhantomData, component, @@ -163,7 +194,7 @@ impl ReflectCommandExt for EntityCommands<'_> { } fn remove_reflect(&mut self, component_type_path: impl Into>) -> &mut Self { - self.commands.add(RemoveReflect { + self.commands.queue(RemoveReflect { entity: self.entity, component_type_path: component_type_path.into(), }); @@ -174,7 +205,7 @@ impl ReflectCommandExt for EntityCommands<'_> { &mut self, component_type_name: impl Into>, ) -> &mut Self { - self.commands.add(RemoveReflectWithRegistry:: { + self.commands.queue(RemoveReflectWithRegistry:: { entity: self.entity, _t: PhantomData, component_type_name: component_type_name.into(), @@ -183,7 +214,7 @@ impl ReflectCommandExt for EntityCommands<'_> { } } -/// Helper function to add a reflect component to a given entity +/// Helper function to add a reflect component or bundle to a given entity fn insert_reflect( world: &mut World, entity: Entity, @@ -198,22 +229,27 @@ fn insert_reflect( panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003"); }; let Some(type_registration) = type_registry.get(type_info.type_id()) else { - panic!("Could not get type registration (for component type {type_path}) because it doesn't exist in the TypeRegistry."); + panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`"); }; - let Some(reflect_component) = type_registration.data::() else { - panic!("Could not get ReflectComponent data (for component type {type_path}) because it doesn't exist in this TypeRegistration."); - }; - reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry); + + if let Some(reflect_component) = type_registration.data::() { + reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry); + } else if let Some(reflect_bundle) = type_registration.data::() { + reflect_bundle.insert(&mut entity, component.as_partial_reflect(), type_registry); + } else { + panic!("`{type_path}` should have #[reflect(Component)] or #[reflect(Bundle)]"); + } } -/// A [`Command`] that adds the boxed reflect component to an entity using the data in +/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in /// [`AppTypeRegistry`]. /// /// See [`ReflectCommandExt::insert_reflect`] for details. pub struct InsertReflect { /// The entity on which the component will be inserted. pub entity: Entity, - /// The reflect [`Component`](crate::component::Component) that will be added to the entity. + /// The reflect [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) + /// that will be added to the entity. pub component: Box, } @@ -224,7 +260,7 @@ impl Command for InsertReflect { } } -/// A [`Command`] that adds the boxed reflect component to an entity using the data in the provided +/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in the provided /// [`Resource`] that implements [`AsRef`]. /// /// See [`ReflectCommandExt::insert_reflect_with_registry`] for details. @@ -245,7 +281,7 @@ impl> Command for InsertReflectWithRegistry } } -/// Helper function to remove a reflect component from a given entity +/// Helper function to remove a reflect component or bundle from a given entity fn remove_reflect( world: &mut World, entity: Entity, @@ -258,20 +294,22 @@ fn remove_reflect( let Some(type_registration) = type_registry.get_with_type_path(&component_type_path) else { return; }; - let Some(reflect_component) = type_registration.data::() else { - return; - }; - reflect_component.remove(&mut entity); + if let Some(reflect_component) = type_registration.data::() { + reflect_component.remove(&mut entity); + } else if let Some(reflect_bundle) = type_registration.data::() { + reflect_bundle.remove(&mut entity); + } } -/// A [`Command`] that removes the component of the same type as the given component type name from +/// A [`Command`] that removes the component or bundle of the same type as the given type name from /// the provided entity. /// /// See [`ReflectCommandExt::remove_reflect`] for details. pub struct RemoveReflect { /// The entity from which the component will be removed. pub entity: Entity, - /// The [`Component`](crate::component::Component) type name that will be used to remove a component + /// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) + /// type name that will be used to remove a component /// of the same type from the entity. pub component_type_path: Cow<'static, str>, } @@ -288,7 +326,7 @@ impl Command for RemoveReflect { } } -/// A [`Command`] that removes the component of the same type as the given component type name from +/// A [`Command`] that removes the component or bundle of the same type as the given type name from /// the provided entity using the provided [`Resource`] that implements [`AsRef`]. /// /// See [`ReflectCommandExt::remove_reflect_with_registry`] for details. @@ -296,7 +334,8 @@ pub struct RemoveReflectWithRegistry> { /// The entity from which the component will be removed. pub entity: Entity, pub _t: PhantomData, - /// The [`Component`](crate::component::Component) type name that will be used to remove a component + /// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle) + /// type name that will be used to remove a component /// of the same type from the entity. pub component_type_name: Cow<'static, str>, } @@ -313,9 +352,9 @@ impl> Command for RemoveReflectWithRegistry #[cfg(test)] mod tests { use crate::prelude::{AppTypeRegistry, ReflectComponent}; - use crate::reflect::ReflectCommandExt; + use crate::reflect::{ReflectBundle, ReflectCommandExt}; use crate::system::{Commands, SystemState}; - use crate::{self as bevy_ecs, component::Component, world::World}; + use crate::{self as bevy_ecs, bundle::Bundle, component::Component, world::World}; use bevy_ecs_macros::Resource; use bevy_reflect::{PartialReflect, Reflect, TypeRegistry}; @@ -334,6 +373,17 @@ mod tests { #[reflect(Component)] struct ComponentA(u32); + #[derive(Component, Reflect, Default, PartialEq, Eq, Debug)] + #[reflect(Component)] + struct ComponentB(u32); + + #[derive(Bundle, Reflect, Default, Debug, PartialEq)] + #[reflect(Bundle)] + struct BundleA { + a: ComponentA, + b: ComponentB, + } + #[test] fn insert_reflected() { let mut world = World::new(); @@ -458,4 +508,140 @@ mod tests { assert_eq!(world.entity(entity).get::(), None); } + + #[test] + fn insert_reflect_bundle() { + let mut world = World::new(); + + let type_registry = AppTypeRegistry::default(); + { + let mut registry = type_registry.write(); + registry.register::(); + registry.register_type_data::(); + } + world.insert_resource(type_registry); + + let mut system_state: SystemState = SystemState::new(&mut world); + let mut commands = system_state.get_mut(&mut world); + + let entity = commands.spawn_empty().id(); + let bundle = Box::new(BundleA { + a: ComponentA(31), + b: ComponentB(20), + }) as Box; + commands.entity(entity).insert_reflect(bundle); + + system_state.apply(&mut world); + + assert_eq!(world.get::(entity), Some(&ComponentA(31))); + assert_eq!(world.get::(entity), Some(&ComponentB(20))); + } + + #[test] + fn insert_reflect_bundle_with_registry() { + let mut world = World::new(); + + let mut type_registry = TypeRegistryResource { + type_registry: TypeRegistry::new(), + }; + + type_registry.type_registry.register::(); + type_registry + .type_registry + .register_type_data::(); + world.insert_resource(type_registry); + + let mut system_state: SystemState = SystemState::new(&mut world); + let mut commands = system_state.get_mut(&mut world); + + let entity = commands.spawn_empty().id(); + let bundle = Box::new(BundleA { + a: ComponentA(31), + b: ComponentB(20), + }) as Box; + + commands + .entity(entity) + .insert_reflect_with_registry::(bundle); + system_state.apply(&mut world); + + assert_eq!(world.get::(entity), Some(&ComponentA(31))); + assert_eq!(world.get::(entity), Some(&ComponentB(20))); + } + + #[test] + fn remove_reflected_bundle() { + let mut world = World::new(); + + let type_registry = AppTypeRegistry::default(); + { + let mut registry = type_registry.write(); + registry.register::(); + registry.register_type_data::(); + } + world.insert_resource(type_registry); + + let mut system_state: SystemState = SystemState::new(&mut world); + let mut commands = system_state.get_mut(&mut world); + + let entity = commands + .spawn(BundleA { + a: ComponentA(31), + b: ComponentB(20), + }) + .id(); + + let boxed_reflect_bundle_a = Box::new(BundleA { + a: ComponentA(1), + b: ComponentB(23), + }) as Box; + + commands + .entity(entity) + .remove_reflect(boxed_reflect_bundle_a.reflect_type_path().to_owned()); + system_state.apply(&mut world); + + assert_eq!(world.entity(entity).get::(), None); + assert_eq!(world.entity(entity).get::(), None); + } + + #[test] + fn remove_reflected_bundle_with_registry() { + let mut world = World::new(); + + let mut type_registry = TypeRegistryResource { + type_registry: TypeRegistry::new(), + }; + + type_registry.type_registry.register::(); + type_registry + .type_registry + .register_type_data::(); + world.insert_resource(type_registry); + + let mut system_state: SystemState = SystemState::new(&mut world); + let mut commands = system_state.get_mut(&mut world); + + let entity = commands + .spawn(BundleA { + a: ComponentA(31), + b: ComponentB(20), + }) + .id(); + + let boxed_reflect_bundle_a = Box::new(BundleA { + a: ComponentA(1), + b: ComponentB(23), + }) as Box; + + commands + .entity(entity) + .remove_reflect_with_registry::( + boxed_reflect_bundle_a.reflect_type_path().to_owned(), + ); + system_state.apply(&mut world); + + assert_eq!(world.entity(entity).get::(), None); + assert_eq!(world.entity(entity).get::(), None); + } } diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 55f38ca19c..1fddf7e1a9 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -33,21 +33,6 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) { } } -impl IntoSystemConfigs for F -where - F: IntoSystem<(), (), Marker>, -{ - fn into_configs(self) -> SystemConfigs { - SystemConfigs::new_system(Box::new(IntoSystem::into_system(self))) - } -} - -impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { - fn into_configs(self) -> SystemConfigs { - SystemConfigs::new_system(self) - } -} - /// Stores configuration for a single generic node (a system or a system set) /// /// The configuration includes the node itself, scheduling metadata @@ -311,8 +296,8 @@ where /// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like /// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead. /// - /// Note: The given set is not implicitly added to the schedule when this system set is added. - /// It is safe, but no dependencies will be created. + /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule. + /// Please check the [caveats section of `.after`](Self::after) for details. fn before(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().before(set) } @@ -323,8 +308,23 @@ where /// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like /// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead. /// - /// Note: The given set is not implicitly added to the schedule when this system set is added. - /// It is safe, but no dependencies will be created. + /// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule. + /// + /// # Caveats + /// + /// If you configure two [`System`]s like `(GameSystem::A).after(GameSystem::B)` or `(GameSystem::A).before(GameSystem::B)`, the `GameSystem::B` will not be automatically scheduled. + /// + /// This means that the system `GameSystem::A` and the system or systems in `GameSystem::B` will run independently of each other if `GameSystem::B` was never explicitly scheduled with [`configure_sets`] + /// If that is the case, `.after`/`.before` will not provide the desired behaviour + /// and the systems can run in parallel or in any order determined by the scheduler. + /// Only use `after(GameSystem::B)` and `before(GameSystem::B)` when you know that `B` has already been scheduled for you, + /// e.g. when it was provided by Bevy or a third-party dependency, + /// or you manually scheduled it somewhere else in your app. + /// + /// Another caveat is that if `GameSystem::B` is placed in a different schedule than `GameSystem::A`, + /// any ordering calls between them—whether using `.before`, `.after`, or `.chain`—will be silently ignored. + /// + /// [`configure_sets`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.configure_sets fn after(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().after(set) } @@ -517,6 +517,21 @@ impl IntoSystemConfigs<()> for SystemConfigs { } } +impl IntoSystemConfigs for F +where + F: IntoSystem<(), (), Marker>, +{ + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(Box::new(IntoSystem::into_system(self))) + } +} + +impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(self) + } +} + #[doc(hidden)] pub struct SystemConfigTupleMarker; diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 39606d998e..c0727b98d1 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -613,9 +613,6 @@ impl ExecutorState { /// # Safety /// Caller must ensure no systems are currently borrowed. unsafe fn spawn_exclusive_system_task(&mut self, context: &Context, system_index: usize) { - // SAFETY: `can_run` returned true for this system, which means - // that no other systems currently have access to the world. - let world = unsafe { context.environment.world_cell.world_mut() }; // SAFETY: this system is not running, no other reference exists let system = unsafe { &mut *context.environment.systems[system_index].get() }; // Move the full context object into the new future. @@ -626,6 +623,9 @@ impl ExecutorState { let unapplied_systems = self.unapplied_systems.clone(); self.unapplied_systems.clear(); let task = async move { + // SAFETY: `can_run` returned true for this system, which means + // that no other systems currently have access to the world. + let world = unsafe { context.environment.world_cell.world_mut() }; let res = apply_deferred(&unapplied_systems, context.environment.systems, world); context.system_completed(system_index, res, system); }; @@ -633,6 +633,9 @@ impl ExecutorState { context.scope.spawn_on_scope(task); } else { let task = async move { + // SAFETY: `can_run` returned true for this system, which means + // 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(|| { __rust_begin_short_backtrace::run(&mut **system, world); })); @@ -783,4 +786,16 @@ mod tests { schedule.run(&mut world); assert!(world.get_resource::().is_some()); } + + /// Regression test for a weird bug flagged by MIRI in + /// `spawn_exclusive_system_task`, related to a `&mut World` being captured + /// inside an `async` block and somehow remaining alive even after its last use. + #[test] + fn check_spawn_exclusive_system_task_miri() { + let mut world = World::new(); + let mut schedule = Schedule::default(); + schedule.set_executor_kind(ExecutorKind::MultiThreaded); + schedule.add_systems(((|_: Commands| {}), |_: Commands| {}).chain()); + schedule.run(&mut world); + } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index dad5d99c56..b9ffa1b914 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1608,7 +1608,7 @@ impl ScheduleGraph { } }; if self.settings.use_shortnames { - name = bevy_utils::get_short_name(&name); + name = bevy_utils::ShortName(&name).to_string(); } name } diff --git a/crates/bevy_ecs/src/storage/blob_array.rs b/crates/bevy_ecs/src/storage/blob_array.rs new file mode 100644 index 0000000000..9970d82e75 --- /dev/null +++ b/crates/bevy_ecs/src/storage/blob_array.rs @@ -0,0 +1,495 @@ +use super::blob_vec::array_layout; +use crate::storage::blob_vec::array_layout_unchecked; +use bevy_ptr::{OwningPtr, Ptr, PtrMut}; +use bevy_utils::OnDrop; +use std::{ + alloc::{handle_alloc_error, Layout}, + cell::UnsafeCell, + num::NonZeroUsize, + ptr::NonNull, +}; + +/// A flat, type-erased data storage type similar to a [`BlobVec`](super::blob_vec::BlobVec), but with the length and capacity cut out +/// for performance reasons. This type is reliant on its owning type to store the capacity and length information. +/// +/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and +/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type only stores meta-data about the Blob that it stores, +/// and a pointer to the location of the start of the array, similar to a C array. +pub(super) struct BlobArray { + item_layout: Layout, + // the `data` ptr's layout is always `array_layout(item_layout, capacity)` + data: NonNull, + // None if the underlying type doesn't need to be dropped + pub drop: Option)>, + #[cfg(debug_assertions)] + capacity: usize, +} + +impl BlobArray { + /// Create a new [`BlobArray`] with a specified `capacity`. + /// If `capacity` is 0, no allocations will be made. + /// + /// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobArray`] + /// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`] + /// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup + /// processes typically associated with the stored element. + /// + /// # Safety + /// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been placed into this [`BlobArray`]. + /// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`]. + /// + /// [`needs_drop`]: core::mem::needs_drop + pub unsafe fn with_capacity( + item_layout: Layout, + drop_fn: Option)>, + capacity: usize, + ) -> Self { + if capacity == 0 { + let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0"); + let data = bevy_ptr::dangling_with_align(align); + Self { + item_layout, + drop: drop_fn, + data, + #[cfg(debug_assertions)] + capacity, + } + } else { + let mut arr = Self::with_capacity(item_layout, drop_fn, 0); + // SAFETY: `capacity` > 0 + unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) } + arr + } + } + + /// Returns the [`Layout`] of the element type stored in the vector. + #[inline] + pub fn layout(&self) -> Layout { + self.item_layout + } + + /// Return `true` if this [`BlobArray`] stores `ZSTs`. + pub fn is_zst(&self) -> bool { + self.item_layout.size() == 0 + } + + /// Returns a reference to the element at `index`, without doing bounds checking. + /// + /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read. + /// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).* + /// + /// # Safety + /// - The element at index `index` is safe to access. + /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`) + #[inline] + pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> { + #[cfg(debug_assertions)] + debug_assert!(index < self.capacity); + let size = self.item_layout.size(); + // SAFETY: + // - The caller ensures that `index` fits in this array, + // so this operation will not overflow the original allocation. + // - `size` is a multiple of the erased type's alignment, + // so adding a multiple of `size` will preserve alignment. + unsafe { self.get_ptr().byte_add(index * size) } + } + + /// Returns a mutable reference to the element at `index`, without doing bounds checking. + /// + /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read. + /// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).* + /// + /// # Safety + /// - The element with at index `index` is safe to access. + /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`) + #[inline] + pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> { + #[cfg(debug_assertions)] + debug_assert!(index < self.capacity); + let size = self.item_layout.size(); + // SAFETY: + // - The caller ensures that `index` fits in this vector, + // so this operation will not overflow the original allocation. + // - `size` is a multiple of the erased type's alignment, + // so adding a multiple of `size` will preserve alignment. + unsafe { self.get_ptr_mut().byte_add(index * size) } + } + + /// Gets a [`Ptr`] to the start of the array + #[inline] + pub fn get_ptr(&self) -> Ptr<'_> { + // SAFETY: the inner data will remain valid for as long as 'self. + unsafe { Ptr::new(self.data) } + } + + /// Gets a [`PtrMut`] to the start of the array + #[inline] + pub fn get_ptr_mut(&mut self) -> PtrMut<'_> { + // SAFETY: the inner data will remain valid for as long as 'self. + unsafe { PtrMut::new(self.data) } + } + + /// Get a slice of the first `slice_len` elements in [`BlobArray`] as if it were an array with elements of type `T` + /// To get a slice to the entire array, the caller must plug `len` in `slice_len`. + /// + /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read. + /// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).* + /// + /// # Safety + /// - The type `T` must be the type of the items in this [`BlobArray`]. + /// - `slice_len` <= `len` + pub unsafe fn get_sub_slice(&self, slice_len: usize) -> &[UnsafeCell] { + #[cfg(debug_assertions)] + debug_assert!(slice_len <= self.capacity); + // SAFETY: the inner data will remain valid for as long as 'self. + unsafe { std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell, slice_len) } + } + + /// Clears the array, i.e. removing (and dropping) all of the elements. + /// Note that this method has no effect on the allocated capacity of the vector. + /// + /// Note that this method will behave exactly the same as [`Vec::clear`]. + /// + /// # Safety + /// - For every element with index `i`, if `i` < `len`: It must be safe to call [`Self::get_unchecked_mut`] with `i`. + /// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `len` is correct.) + pub unsafe fn clear(&mut self, len: usize) { + #[cfg(debug_assertions)] + debug_assert!(self.capacity >= len); + if let Some(drop) = self.drop { + // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't + // accidentally drop elements twice in the event of a drop impl panicking. + self.drop = None; + let size = self.item_layout.size(); + for i in 0..len { + // SAFETY: + // * 0 <= `i` < `len`, so `i * size` must be in bounds for the allocation. + // * `size` is a multiple of the erased type's alignment, + // so adding a multiple of `size` will preserve alignment. + // * The item is left unreachable so it can be safely promoted to an `OwningPtr`. + let item = unsafe { self.get_ptr_mut().byte_add(i * size).promote() }; + // SAFETY: `item` was obtained from this `BlobArray`, so its underlying type must match `drop`. + unsafe { drop(item) }; + } + self.drop = Some(drop); + } + } + + /// Because this method needs parameters, it can't be the implementation of the `Drop` trait. + /// The owner of this [`BlobArray`] must call this method with the correct information. + /// + /// # Safety + /// - `cap` and `len` are indeed the capacity and length of this [`BlobArray`] + /// - This [`BlobArray`] mustn't be used after calling this method. + pub unsafe fn drop(&mut self, cap: usize, len: usize) { + #[cfg(debug_assertions)] + debug_assert_eq!(self.capacity, cap); + if cap != 0 { + self.clear(len); + if !self.is_zst() { + let layout = + array_layout(&self.item_layout, cap).expect("array layout should be valid"); + std::alloc::dealloc(self.data.as_ptr().cast(), layout); + } + #[cfg(debug_assertions)] + { + self.capacity = 0; + } + } + } + + /// Drops the last element in this [`BlobArray`]. + /// + /// # Safety + // - `last_element_index` must correspond to the last element in the array. + // - After this method is called, the last element must not be used + // unless [`Self::initialize_unchecked`] is called to set the value of the last element. + pub unsafe fn drop_last_element(&mut self, last_element_index: usize) { + #[cfg(debug_assertions)] + debug_assert!(self.capacity > last_element_index); + if let Some(drop) = self.drop { + // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't + // accidentally drop elements twice in the event of a drop impl panicking. + self.drop = None; + // SAFETY: + let item = self.get_unchecked_mut(last_element_index).promote(); + // SAFETY: + unsafe { drop(item) }; + self.drop = Some(drop); + } + } + + /// Allocate a block of memory for the array. This should be used to initialize the array, do not use this + /// method if there are already elements stored in the array - use [`Self::realloc`] instead. + pub(super) fn alloc(&mut self, capacity: NonZeroUsize) { + #[cfg(debug_assertions)] + debug_assert_eq!(self.capacity, 0); + if !self.is_zst() { + let new_layout = array_layout(&self.item_layout, capacity.get()) + .expect("array layout should be valid"); + // SAFETY: layout has non-zero size because capacity > 0, and the blob isn't ZST (`self.is_zst` == false) + let new_data = unsafe { std::alloc::alloc(new_layout) }; + self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)); + } + #[cfg(debug_assertions)] + { + self.capacity = capacity.into(); + } + } + + /// Reallocate memory for this array. + /// For example, if the length (number of stored elements) reached the capacity (number of elements the current allocation can store), + /// you might want to use this method to increase the allocation, so more data can be stored in the array. + /// + /// # Safety + /// - `current_capacity` is indeed the current capacity of this array. + /// - After calling this method, the caller must update their saved capacity to reflect the change. + pub(super) unsafe fn realloc( + &mut self, + current_capacity: NonZeroUsize, + new_capacity: NonZeroUsize, + ) { + #[cfg(debug_assertions)] + debug_assert_eq!(self.capacity, current_capacity.into()); + if !self.is_zst() { + // SAFETY: `new_capacity` can't overflow usize + let new_layout = + unsafe { array_layout_unchecked(&self.item_layout, new_capacity.get()) }; + // SAFETY: + // - ptr was be allocated via this allocator + // - the layout used to previously allocate this array is equivalent to `array_layout(&self.item_layout, current_capacity.get())` + // - `item_layout.size() > 0` (`self.is_zst`==false) and `new_capacity > 0`, so the layout size is non-zero + // - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)", + // since the item size is always a multiple of its align, the rounding cannot happen + // here and the overflow is handled in `array_layout` + let new_data = unsafe { + std::alloc::realloc( + self.get_ptr_mut().as_ptr(), + // SAFETY: This is the Layout of the current array, it must be valid, if it hadn't have been, there would have been a panic on a previous allocation + array_layout_unchecked(&self.item_layout, current_capacity.get()), + new_layout.size(), + ) + }; + self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)); + } + #[cfg(debug_assertions)] + { + self.capacity = new_capacity.into(); + } + } + + /// Initializes the value at `index` to `value`. This function does not do any bounds checking. + /// + /// # Safety + /// - `index` must be in bounds (`index` < capacity) + /// - The [`Layout`] of the value must match the layout of the blobs stored in this array, + /// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`. + /// - `value` must not point to the same value that is being initialized. + #[inline] + pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) { + #[cfg(debug_assertions)] + debug_assert!(self.capacity > index); + let size = self.item_layout.size(); + let dst = self.get_unchecked_mut(index); + std::ptr::copy::(value.as_ptr(), dst.as_ptr(), size); + } + + /// Replaces the value at `index` with `value`. This function does not do any bounds checking. + /// + /// # Safety + /// - Index must be in-bounds (`index` < `len`) + /// - `value`'s [`Layout`] must match this [`BlobArray`]'s `item_layout`, + /// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`. + /// - `value` must not point to the same value that is being replaced. + pub unsafe fn replace_unchecked(&mut self, index: usize, value: OwningPtr<'_>) { + #[cfg(debug_assertions)] + debug_assert!(self.capacity > index); + // Pointer to the value in the vector that will get replaced. + // SAFETY: The caller ensures that `index` fits in this vector. + let destination = NonNull::from(unsafe { self.get_unchecked_mut(index) }); + let source = value.as_ptr(); + + if let Some(drop) = self.drop { + // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't + // accidentally drop elements twice in the event of a drop impl panicking. + self.drop = None; + + // Transfer ownership of the old value out of the vector, so it can be dropped. + // SAFETY: + // - `destination` was obtained from a `PtrMut` in this vector, which ensures it is non-null, + // well-aligned for the underlying type, and has proper provenance. + // - The storage location will get overwritten with `value` later, which ensures + // that the element will not get observed or double dropped later. + // - If a panic occurs, `self.len` will remain `0`, which ensures a double-drop + // does not occur. Instead, all elements will be forgotten. + let old_value = unsafe { OwningPtr::new(destination) }; + + // This closure will run in case `drop()` panics, + // which ensures that `value` does not get forgotten. + let on_unwind = OnDrop::new(|| drop(value)); + + drop(old_value); + + // If the above code does not panic, make sure that `value` doesn't get dropped. + core::mem::forget(on_unwind); + + self.drop = Some(drop); + } + + // Copy the new value into the vector, overwriting the previous value. + // SAFETY: + // - `source` and `destination` were obtained from `OwningPtr`s, which ensures they are + // valid for both reads and writes. + // - The value behind `source` will only be dropped if the above branch panics, + // so it must still be initialized and it is safe to transfer ownership into the vector. + // - `source` and `destination` were obtained from different memory locations, + // both of which we have exclusive access to, so they are guaranteed not to overlap. + unsafe { + std::ptr::copy_nonoverlapping::( + source, + destination.as_ptr(), + self.item_layout.size(), + ); + } + } + + /// This method will swap two elements in the array, and return the one at `index_to_remove`. + /// It is the caller's responsibility to drop the returned pointer, if that is desirable. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + #[must_use = "The returned pointer should be used to drop the removed element"] + pub unsafe fn swap_remove_unchecked( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) -> OwningPtr<'_> { + #[cfg(debug_assertions)] + { + debug_assert!(self.capacity > index_to_keep); + debug_assert!(self.capacity > index_to_remove); + } + if index_to_remove != index_to_keep { + return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep); + } + // Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap) + // If we are storing ZSTs than the index doesn't actually matter because the size is 0. + self.get_unchecked_mut(index_to_keep).promote() + } + + /// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_unchecked_nonoverlapping( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) -> OwningPtr<'_> { + #[cfg(debug_assertions)] + { + debug_assert!(self.capacity > index_to_keep); + debug_assert!(self.capacity > index_to_remove); + debug_assert_ne!(index_to_keep, index_to_remove); + } + debug_assert_ne!(index_to_keep, index_to_remove); + std::ptr::swap_nonoverlapping::( + self.get_unchecked_mut(index_to_keep).as_ptr(), + self.get_unchecked_mut(index_to_remove).as_ptr(), + self.item_layout.size(), + ); + // Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap) + // If we are storing ZSTs than the index doesn't actually matter because the size is 0. + self.get_unchecked_mut(index_to_keep).promote() + } + + /// This method will can [`Self::swap_remove_unchecked`] and drop the result. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_and_drop_unchecked( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) { + #[cfg(debug_assertions)] + { + debug_assert!(self.capacity > index_to_keep); + debug_assert!(self.capacity > index_to_remove); + } + let drop = self.drop; + let value = self.swap_remove_unchecked(index_to_remove, index_to_keep); + if let Some(drop) = drop { + drop(value); + } + } + + /// The same as [`Self::swap_remove_and_drop_unchecked`] but the two elements must non-overlapping. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) { + #[cfg(debug_assertions)] + { + debug_assert!(self.capacity > index_to_keep); + debug_assert!(self.capacity > index_to_remove); + debug_assert_ne!(index_to_keep, index_to_remove); + } + let drop = self.drop; + let value = self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep); + if let Some(drop) = drop { + drop(value); + } + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use bevy_ecs::prelude::*; + + #[derive(Component)] + struct PanicOnDrop; + + impl Drop for PanicOnDrop { + fn drop(&mut self) { + panic!("PanicOnDrop is being Dropped"); + } + } + + #[test] + #[should_panic(expected = "PanicOnDrop is being Dropped")] + fn make_sure_zst_components_get_dropped() { + let mut world = World::new(); + + world.spawn(PanicOnDrop); + } +} diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index d5699f3716..9d268944e9 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -1,3 +1,5 @@ +use bevy_ptr::{OwningPtr, Ptr, PtrMut}; +use bevy_utils::OnDrop; use std::{ alloc::{handle_alloc_error, Layout}, cell::UnsafeCell, @@ -5,9 +7,6 @@ use std::{ ptr::NonNull, }; -use bevy_ptr::{OwningPtr, Ptr, PtrMut}; -use bevy_utils::OnDrop; - /// A flat, type-erased data storage type /// /// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and @@ -93,12 +92,6 @@ impl BlobVec { self.len == 0 } - /// Returns the total number of elements the vector can hold without reallocating. - #[inline] - pub fn capacity(&self) -> usize { - self.capacity - } - /// Returns the [`Layout`] of the element type stored in the vector. #[inline] pub fn layout(&self) -> Layout { @@ -271,26 +264,12 @@ impl BlobVec { self.initialize_unchecked(index, value); } - /// Forces the length of the vector to `len`. - /// - /// # Safety - /// `len` must be <= `capacity`. if length is decreased, "out of bounds" items must be dropped. - /// Newly added items must be immediately populated with valid values and length must be - /// increased. For better unwind safety, call [`BlobVec::set_len`] _after_ populating a new - /// value. - #[inline] - pub unsafe fn set_len(&mut self, len: usize) { - debug_assert!(len <= self.capacity()); - self.len = len; - } - /// Performs a "swap remove" at the given `index`, which removes the item at `index` and moves /// the last item in the [`BlobVec`] to `index` (if `index` is not the last item). It is the /// caller's responsibility to drop the returned pointer, if that is desirable. /// /// # Safety /// It is the caller's responsibility to ensure that `index` is less than `self.len()`. - #[inline] #[must_use = "The returned pointer should be used to dropped the removed element"] pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> OwningPtr<'_> { debug_assert!(index < self.len()); @@ -318,28 +297,6 @@ impl BlobVec { unsafe { p.promote() } } - /// Removes the value at `index` and copies the value stored into `ptr`. - /// Does not do any bounds checking on `index`. - /// The removed element is replaced by the last element of the `BlobVec`. - /// - /// # Safety - /// It is the caller's responsibility to ensure that `index` is < `self.len()` - /// and that `self[index]` has been properly initialized. - #[inline] - pub unsafe fn swap_remove_unchecked(&mut self, index: usize, ptr: PtrMut<'_>) { - debug_assert!(index < self.len()); - let last = self.get_unchecked_mut(self.len - 1).as_ptr(); - let target = self.get_unchecked_mut(index).as_ptr(); - // Copy the item at the index into the provided ptr - std::ptr::copy_nonoverlapping::(target, ptr.as_ptr(), self.item_layout.size()); - // Recompress the storage by moving the previous last element into the - // now-free row overwriting the previous data. The removed row may be the last - // one so a non-overlapping copy must not be used here. - std::ptr::copy::(last, target, self.item_layout.size()); - // Invalidate the data stored in the last row, as it has been moved - self.len -= 1; - } - /// Removes the value at `index` and drops it. /// Does not do any bounds checking on `index`. /// The removed element is replaced by the last element of the `BlobVec`. @@ -438,14 +395,6 @@ impl BlobVec { } } } - - /// Get the `drop` argument that was passed to `BlobVec::new`. - /// - /// Callers can use this if they have a type-erased pointer of the correct - /// type to add to this [`BlobVec`], which they just want to drop instead. - pub fn get_drop(&self) -> Option)> { - self.drop - } } impl Drop for BlobVec { @@ -463,7 +412,7 @@ impl Drop for BlobVec { } /// From -fn array_layout(layout: &Layout, n: usize) -> Option { +pub(super) fn array_layout(layout: &Layout, n: usize) -> Option { let (array_layout, offset) = repeat_layout(layout, n)?; debug_assert_eq!(layout.size(), offset); Some(array_layout) @@ -489,6 +438,40 @@ fn repeat_layout(layout: &Layout, n: usize) -> Option<(Layout, usize)> { } } +/// From +/// # Safety +/// The caller must ensure that: +/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow. +pub(super) unsafe fn array_layout_unchecked(layout: &Layout, n: usize) -> Layout { + let (array_layout, offset) = repeat_layout_unchecked(layout, n); + debug_assert_eq!(layout.size(), offset); + array_layout +} + +// TODO: replace with `Layout::repeat` if/when it stabilizes +/// From +/// # Safety +/// The caller must ensure that: +/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow. +unsafe fn repeat_layout_unchecked(layout: &Layout, n: usize) -> (Layout, usize) { + // This cannot overflow. Quoting from the invariant of Layout: + // > `size`, when rounded up to the nearest multiple of `align`, + // > must not overflow (i.e., the rounded value must be less than + // > `usize::MAX`) + let padded_size = layout.size() + padding_needed_for(layout, layout.align()); + // This may overflow in release builds, that's why this function is unsafe. + let alloc_size = padded_size * n; + + // SAFETY: self.align is already known to be valid and alloc_size has been + // padded already. + unsafe { + ( + Layout::from_size_align_unchecked(alloc_size, layout.align()), + padded_size, + ) + } +} + /// From const fn padding_needed_for(layout: &Layout, align: usize) -> usize { let len = layout.size(); @@ -522,10 +505,14 @@ mod tests { use crate::{component::Component, ptr::OwningPtr, world::World}; use super::BlobVec; - use std::{alloc::Layout, cell::RefCell, mem::align_of, rc::Rc}; + use std::{alloc::Layout, cell::RefCell, rc::Rc}; + /// # Safety + /// + /// The pointer `x` must point to a valid value of type `T` and it must be safe to drop this value. unsafe fn drop_ptr(x: OwningPtr<'_>) { - // SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value. + // SAFETY: It is guaranteed by the caller that `x` points to a + // valid value of type `T` and it is safe to drop this value. unsafe { x.drop_as::(); } @@ -571,7 +558,7 @@ mod tests { } assert_eq!(blob_vec.len(), 1_000); - assert_eq!(blob_vec.capacity(), 1_024); + assert_eq!(blob_vec.capacity, 1_024); } #[derive(Debug, Eq, PartialEq, Clone)] @@ -595,7 +582,7 @@ mod tests { let drop = drop_ptr::; // SAFETY: drop is able to drop a value of its `item_layout` let mut blob_vec = unsafe { BlobVec::new(item_layout, Some(drop), 2) }; - assert_eq!(blob_vec.capacity(), 2); + assert_eq!(blob_vec.capacity, 2); // SAFETY: the following code only deals with values of type `Foo`, which satisfies the safety requirement of `push`, `get_mut` and `swap_remove` that the // values have a layout compatible to the blob vec's `item_layout`. // Every index is in range. @@ -616,7 +603,7 @@ mod tests { }; push::(&mut blob_vec, foo2.clone()); assert_eq!(blob_vec.len(), 2); - assert_eq!(blob_vec.capacity(), 2); + assert_eq!(blob_vec.capacity, 2); assert_eq!(get_mut::(&mut blob_vec, 0), &foo1); assert_eq!(get_mut::(&mut blob_vec, 1), &foo2); @@ -631,19 +618,19 @@ mod tests { push(&mut blob_vec, foo3.clone()); assert_eq!(blob_vec.len(), 3); - assert_eq!(blob_vec.capacity(), 4); + assert_eq!(blob_vec.capacity, 4); let last_index = blob_vec.len() - 1; let value = swap_remove::(&mut blob_vec, last_index); assert_eq!(foo3, value); assert_eq!(blob_vec.len(), 2); - assert_eq!(blob_vec.capacity(), 4); + assert_eq!(blob_vec.capacity, 4); let value = swap_remove::(&mut blob_vec, 0); assert_eq!(foo1, value); assert_eq!(blob_vec.len(), 1); - assert_eq!(blob_vec.capacity(), 4); + assert_eq!(blob_vec.capacity, 4); foo2.a = 8; assert_eq!(get_mut::(&mut blob_vec, 0), &foo2); @@ -667,14 +654,12 @@ mod tests { // SAFETY: no drop is correct drop for `()`. let mut blob_vec = unsafe { BlobVec::new(Layout::new::<()>(), None, 0) }; - assert_eq!(usize::MAX, blob_vec.capacity(), "Self-check"); + assert_eq!(usize::MAX, blob_vec.capacity, "Self-check"); // SAFETY: Because `()` is a ZST trivial drop type, and because `BlobVec` capacity // is always `usize::MAX` for ZSTs, we can arbitrarily set the length // and still be sound. - unsafe { - blob_vec.set_len(usize::MAX); - } + blob_vec.len = usize::MAX; // SAFETY: `BlobVec` was initialized for `()`, so it is safe to push `()` to it. unsafe { @@ -691,7 +676,7 @@ mod tests { // SAFETY: no drop is correct drop for `u32`. let mut blob_vec = unsafe { BlobVec::new(Layout::new::(), None, 0) }; - assert_eq!(0, blob_vec.capacity(), "Self-check"); + assert_eq!(0, blob_vec.capacity, "Self-check"); OwningPtr::make(17u32, |ptr| { // SAFETY: we push the value of correct type. diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 72b1dced7d..eaeff97a8c 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -20,10 +20,12 @@ //! [`World`]: crate::world::World //! [`World::storages`]: crate::world::World::storages +mod blob_array; mod blob_vec; mod resource; mod sparse_set; mod table; +mod thin_array_ptr; pub use resource::*; pub use sparse_set::*; diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index 82a1a54d48..fec0ca86d6 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -25,7 +25,10 @@ pub struct ResourceData { impl Drop for ResourceData { fn drop(&mut self) { - if self.is_present() { + // For Non Send resources we need to validate that correct thread + // is dropping the resource. This validation is not needed in case + // of SEND resources. Or if there is no data. + if !SEND && self.is_present() { // If this thread is already panicking, panicking again will cause // the entire process to abort. In this case we choose to avoid // dropping or checking this altogether and just leak the column. diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs deleted file mode 100644 index d525cebe21..0000000000 --- a/crates/bevy_ecs/src/storage/table.rs +++ /dev/null @@ -1,1076 +0,0 @@ -use crate::{ - change_detection::{MaybeLocation, MaybeUnsafeCellLocation}, - component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells}, - entity::Entity, - query::DebugCheckedUnwrap, - storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, -}; -use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref}; -use bevy_utils::HashMap; -use std::alloc::Layout; -#[cfg(feature = "track_change_detection")] -use std::panic::Location; -use std::{ - cell::UnsafeCell, - ops::{Index, IndexMut}, -}; - -/// An opaque unique ID for a [`Table`] within a [`World`]. -/// -/// Can be used with [`Tables::get`] to fetch the corresponding -/// table. -/// -/// Each [`Archetype`] always points to a table via [`Archetype::table_id`]. -/// Multiple archetypes can point to the same table so long as the components -/// stored in the table are identical, but do not share the same sparse set -/// components. -/// -/// [`World`]: crate::world::World -/// [`Archetype`]: crate::archetype::Archetype -/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation -#[repr(transparent)] -pub struct TableId(u32); - -impl TableId { - pub(crate) const INVALID: TableId = TableId(u32::MAX); - - /// Creates a new [`TableId`]. - /// - /// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got - /// from a table of a given [`World`] or the created ID may be invalid. - /// - /// [`World`]: crate::world::World - #[inline] - pub const fn from_u32(index: u32) -> Self { - Self(index) - } - - /// Creates a new [`TableId`]. - /// - /// `index` *must* be retrieved from calling [`TableId::as_usize`] on a `TableId` you got - /// from a table of a given [`World`] or the created ID may be invalid. - /// - /// [`World`]: crate::world::World - /// - /// # Panics - /// - /// Will panic if the provided value does not fit within a [`u32`]. - #[inline] - pub const fn from_usize(index: usize) -> Self { - debug_assert!(index as u32 as usize == index); - Self(index as u32) - } - - /// Gets the underlying table index from the ID. - #[inline] - pub const fn as_u32(self) -> u32 { - self.0 - } - - /// Gets the underlying table index from the ID. - #[inline] - pub const fn as_usize(self) -> usize { - // usize is at least u32 in Bevy - self.0 as usize - } - - /// The [`TableId`] of the [`Table`] without any components. - #[inline] - pub const fn empty() -> Self { - Self(0) - } -} - -/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table. -/// -/// Values of this type are retrievable from [`Archetype::entity_table_row`] and can be -/// used alongside [`Archetype::table_id`] to fetch the exact table and row where an -/// [`Entity`]'s -/// -/// Values of this type are only valid so long as entities have not moved around. -/// Adding and removing components from an entity, or despawning it will invalidate -/// potentially any table row in the table the entity was previously stored in. Users -/// should *always* fetch the appropriate row from the entity's [`Archetype`] before -/// fetching the entity's components. -/// -/// [`Archetype`]: crate::archetype::Archetype -/// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row -/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation -#[repr(transparent)] -pub struct TableRow(u32); - -impl TableRow { - pub(crate) const INVALID: TableRow = TableRow(u32::MAX); - - /// Creates a `TableRow`. - #[inline] - pub const fn from_u32(index: u32) -> Self { - Self(index) - } - - /// Creates a `TableRow` from a [`usize`] index. - /// - /// # Panics - /// - /// Will panic if the provided value does not fit within a [`u32`]. - #[inline] - pub const fn from_usize(index: usize) -> Self { - debug_assert!(index as u32 as usize == index); - Self(index as u32) - } - - /// Gets the index of the row as a [`usize`]. - #[inline] - pub const fn as_usize(self) -> usize { - // usize is at least u32 in Bevy - self.0 as usize - } - - /// Gets the index of the row as a [`usize`]. - #[inline] - pub const fn as_u32(self) -> u32 { - self.0 - } -} - -/// A type-erased contiguous container for data of a homogeneous type. -/// -/// Conceptually, a [`Column`] is very similar to a type-erased `Vec`. -/// It also stores the change detection ticks for its components, kept in two separate -/// contiguous buffers internally. An element shares its data across these buffers by using the -/// same index (i.e. the entity at row 3 has its data at index 3 and its change detection ticks at -/// index 3). A slice to these contiguous blocks of memory can be fetched -/// via [`Column::get_data_slice`], [`Column::get_added_ticks_slice`], and -/// [`Column::get_changed_ticks_slice`]. -/// -/// Like many other low-level storage types, [`Column`] has a limited and highly unsafe -/// interface. It's highly advised to use higher level types and their safe abstractions -/// instead of working directly with [`Column`]. -#[derive(Debug)] -pub struct Column { - data: BlobVec, - added_ticks: Vec>, - changed_ticks: Vec>, - #[cfg(feature = "track_change_detection")] - changed_by: Vec>>, -} - -impl Column { - /// Constructs a new [`Column`], configured with a component's layout and an initial `capacity`. - #[inline] - pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self { - Column { - // SAFETY: component_info.drop() is valid for the types that will be inserted. - data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, - added_ticks: Vec::with_capacity(capacity), - changed_ticks: Vec::with_capacity(capacity), - #[cfg(feature = "track_change_detection")] - changed_by: Vec::with_capacity(capacity), - } - } - - /// Fetches the [`Layout`] for the underlying type. - #[inline] - pub fn item_layout(&self) -> Layout { - self.data.layout() - } - - /// Writes component data to the column at given row. - /// Assumes the slot is uninitialized, drop is not called. - /// To overwrite existing initialized value, use `replace` instead. - /// - /// # Safety - /// Assumes data has already been allocated for the given row. - #[inline] - pub(crate) unsafe fn initialize( - &mut self, - row: TableRow, - data: OwningPtr<'_>, - tick: Tick, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, - ) { - debug_assert!(row.as_usize() < self.len()); - self.data.initialize_unchecked(row.as_usize(), data); - *self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick; - *self - .changed_ticks - .get_unchecked_mut(row.as_usize()) - .get_mut() = tick; - #[cfg(feature = "track_change_detection")] - { - *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; - } - } - - /// Writes component data to the column at given row. - /// Assumes the slot is initialized, calls drop. - /// - /// # Safety - /// Assumes data has already been allocated for the given row. - #[inline] - pub(crate) unsafe fn replace( - &mut self, - row: TableRow, - data: OwningPtr<'_>, - change_tick: Tick, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, - ) { - debug_assert!(row.as_usize() < self.len()); - self.data.replace_unchecked(row.as_usize(), data); - *self - .changed_ticks - .get_unchecked_mut(row.as_usize()) - .get_mut() = change_tick; - - #[cfg(feature = "track_change_detection")] - { - *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; - } - } - - /// Call [`drop`] on a value. - /// - /// # Safety - /// `data` must point to the same type that this table stores, so the - /// correct drop function is called. - #[inline] - pub(crate) unsafe fn drop(&self, data: OwningPtr<'_>) { - if let Some(drop) = self.data.get_drop() { - // Safety: we're using the same drop fn that the BlobVec would - // if we inserted the data instead of dropping it. - unsafe { drop(data) } - } - } - - /// Gets the current number of elements stored in the column. - #[inline] - pub fn len(&self) -> usize { - self.data.len() - } - - /// Checks if the column is empty. Returns `true` if there are no elements, `false` otherwise. - #[inline] - pub fn is_empty(&self) -> bool { - self.data.is_empty() - } - - /// Removes an element from the [`Column`]. - /// - /// - The value will be dropped if it implements [`Drop`]. - /// - This does not preserve ordering, but is O(1). - /// - This does not do any bounds checking. - /// - The element is replaced with the last element in the [`Column`]. - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - /// - #[inline] - pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { - self.data.swap_remove_and_drop_unchecked(row.as_usize()); - self.added_ticks.swap_remove(row.as_usize()); - self.changed_ticks.swap_remove(row.as_usize()); - #[cfg(feature = "track_change_detection")] - self.changed_by.swap_remove(row.as_usize()); - } - - /// Removes an element from the [`Column`] and returns it and its change detection ticks. - /// This does not preserve ordering, but is O(1) and does not do any bounds checking. - /// - /// The element is replaced with the last element in the [`Column`]. - /// - /// It's the caller's responsibility to ensure that the removed value is dropped or used. - /// Failure to do so may result in resources not being released (i.e. files handles not being - /// released, memory leaks, etc.) - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - #[inline] - #[must_use = "The returned pointer should be used to dropped the removed component"] - pub(crate) unsafe fn swap_remove_and_forget_unchecked( - &mut self, - row: TableRow, - ) -> (OwningPtr<'_>, ComponentTicks, MaybeLocation) { - let data = self.data.swap_remove_and_forget_unchecked(row.as_usize()); - let added = self.added_ticks.swap_remove(row.as_usize()).into_inner(); - let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner(); - #[cfg(feature = "track_change_detection")] - let caller = self.changed_by.swap_remove(row.as_usize()).into_inner(); - #[cfg(not(feature = "track_change_detection"))] - let caller = (); - (data, ComponentTicks { added, changed }, caller) - } - - /// Removes the element from `other` at `src_row` and inserts it - /// into the current column to initialize the values at `dst_row`. - /// Does not do any bounds checking. - /// - /// # Safety - /// - /// - `other` must have the same data layout as `self` - /// - `src_row` must be in bounds for `other` - /// - `dst_row` must be in bounds for `self` - /// - `other[src_row]` must be initialized to a valid value. - /// - `self[dst_row]` must not be initialized yet. - #[inline] - pub(crate) unsafe fn initialize_from_unchecked( - &mut self, - other: &mut Column, - src_row: TableRow, - dst_row: TableRow, - ) { - debug_assert!(self.data.layout() == other.data.layout()); - let ptr = self.data.get_unchecked_mut(dst_row.as_usize()); - other.data.swap_remove_unchecked(src_row.as_usize(), ptr); - *self.added_ticks.get_unchecked_mut(dst_row.as_usize()) = - other.added_ticks.swap_remove(src_row.as_usize()); - *self.changed_ticks.get_unchecked_mut(dst_row.as_usize()) = - other.changed_ticks.swap_remove(src_row.as_usize()); - #[cfg(feature = "track_change_detection")] - { - *self.changed_by.get_unchecked_mut(dst_row.as_usize()) = - other.changed_by.swap_remove(src_row.as_usize()); - } - } - - /// Pushes a new value onto the end of the [`Column`]. - /// - /// # Safety - /// `ptr` must point to valid data of this column's component type - pub(crate) unsafe fn push( - &mut self, - ptr: OwningPtr<'_>, - ticks: ComponentTicks, - #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, - ) { - self.data.push(ptr); - self.added_ticks.push(UnsafeCell::new(ticks.added)); - self.changed_ticks.push(UnsafeCell::new(ticks.changed)); - #[cfg(feature = "track_change_detection")] - self.changed_by.push(UnsafeCell::new(caller)); - } - - #[inline] - pub(crate) fn reserve_exact(&mut self, additional: usize) { - self.data.reserve_exact(additional); - self.added_ticks.reserve_exact(additional); - self.changed_ticks.reserve_exact(additional); - #[cfg(feature = "track_change_detection")] - self.changed_by.reserve_exact(additional); - } - - /// Fetches the data pointer to the first element of the [`Column`]. - /// - /// The pointer is type erased, so using this function to fetch anything - /// other than the first element will require computing the offset using - /// [`Column::item_layout`]. - #[inline] - pub fn get_data_ptr(&self) -> Ptr<'_> { - self.data.get_ptr() - } - - /// Fetches the slice to the [`Column`]'s data cast to a given type. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - /// - /// # Safety - /// The type `T` must be the type of the items in this column. - pub unsafe fn get_data_slice(&self) -> &[UnsafeCell] { - self.data.get_slice() - } - - /// Fetches the slice to the [`Column`]'s "added" change detection ticks. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - pub fn get_added_ticks_slice(&self) -> &[UnsafeCell] { - &self.added_ticks - } - - /// Fetches the slice to the [`Column`]'s "changed" change detection ticks. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell] { - &self.changed_ticks - } - - /// Fetches the slice to the [`Column`]'s caller locations. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - #[cfg(feature = "track_change_detection")] - pub fn get_changed_by_slice(&self) -> &[UnsafeCell<&'static Location<'static>>] { - &self.changed_by - } - - /// Fetches a reference to the data and change detection ticks at `row`. - /// - /// Returns `None` if `row` is out of bounds. - #[inline] - pub fn get( - &self, - row: TableRow, - ) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { - (row.as_usize() < self.data.len()) - // SAFETY: The row is length checked before fetching the pointer. This is being - // accessed through a read-only reference to the column. - .then(|| unsafe { - ( - self.data.get_unchecked(row.as_usize()), - TickCells { - added: self.added_ticks.get_unchecked(row.as_usize()), - changed: self.changed_ticks.get_unchecked(row.as_usize()), - }, - #[cfg(feature = "track_change_detection")] - self.changed_by.get_unchecked(row.as_usize()), - #[cfg(not(feature = "track_change_detection"))] - (), - ) - }) - } - - /// Fetches a read-only reference to the data at `row`. - /// - /// Returns `None` if `row` is out of bounds. - #[inline] - pub fn get_data(&self, row: TableRow) -> Option> { - (row.as_usize() < self.data.len()).then(|| { - // SAFETY: The row is length checked before fetching the pointer. This is being - // accessed through a read-only reference to the column. - unsafe { self.data.get_unchecked(row.as_usize()) } - }) - } - - /// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not - /// do any bounds checking. - /// - /// # Safety - /// - `row` must be within the range `[0, self.len())`. - /// - no other mutable reference to the data of the same row can exist at the same time - #[inline] - pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { - debug_assert!(row.as_usize() < self.data.len()); - self.data.get_unchecked(row.as_usize()) - } - - /// Fetches a mutable reference to the data at `row`. - /// - /// Returns `None` if `row` is out of bounds. - #[inline] - pub fn get_data_mut(&mut self, row: TableRow) -> Option> { - (row.as_usize() < self.data.len()).then(|| { - // SAFETY: The row is length checked before fetching the pointer. This is being - // accessed through an exclusive reference to the column. - unsafe { self.data.get_unchecked_mut(row.as_usize()) } - }) - } - - /// Fetches a mutable reference to the data at `row`. Unlike [`Column::get_data_mut`] this does not - /// do any bounds checking. - /// - /// # Safety - /// - index must be in-bounds - /// - no other reference to the data of the same row can exist at the same time - #[inline] - pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: TableRow) -> PtrMut<'_> { - debug_assert!(row.as_usize() < self.data.len()); - self.data.get_unchecked_mut(row.as_usize()) - } - - /// Fetches the "added" change detection tick for the value at `row`. - /// - /// Returns `None` if `row` is out of bounds. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell> { - self.added_ticks.get(row.as_usize()) - } - - /// Fetches the "changed" change detection tick for the value at `row`. - /// - /// Returns `None` if `row` is out of bounds. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell> { - self.changed_ticks.get(row.as_usize()) - } - - /// Fetches the change detection ticks for the value at `row`. - /// - /// Returns `None` if `row` is out of bounds. - #[inline] - pub fn get_ticks(&self, row: TableRow) -> Option { - if row.as_usize() < self.data.len() { - // SAFETY: The size of the column has already been checked. - Some(unsafe { self.get_ticks_unchecked(row) }) - } else { - None - } - } - - /// Fetches the "added" change detection tick for the value at `row`. Unlike [`Column::get_added_tick`] - /// this function does not do any bounds checking. - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - #[inline] - pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { - debug_assert!(row.as_usize() < self.added_ticks.len()); - self.added_ticks.get_unchecked(row.as_usize()) - } - - /// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`] - /// this function does not do any bounds checking. - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - #[inline] - pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { - debug_assert!(row.as_usize() < self.changed_ticks.len()); - self.changed_ticks.get_unchecked(row.as_usize()) - } - - /// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`] - /// this function does not do any bounds checking. - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - #[inline] - pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { - debug_assert!(row.as_usize() < self.added_ticks.len()); - debug_assert!(row.as_usize() < self.changed_ticks.len()); - ComponentTicks { - added: self.added_ticks.get_unchecked(row.as_usize()).read(), - changed: self.changed_ticks.get_unchecked(row.as_usize()).read(), - } - } - - /// Fetches the calling location that last changed the value at `row`. - /// - /// Returns `None` if `row` is out of bounds. - /// - /// Note: The values stored within are [`UnsafeCell`]. - /// Users of this API must ensure that accesses to each individual element - /// adhere to the safety invariants of [`UnsafeCell`]. - #[inline] - #[cfg(feature = "track_change_detection")] - pub fn get_changed_by(&self, row: TableRow) -> Option<&UnsafeCell<&'static Location<'static>>> { - self.changed_by.get(row.as_usize()) - } - - /// Fetches the calling location that last changed the value at `row`. - /// - /// Unlike [`Column::get_changed_by`] this function does not do any bounds checking. - /// - /// # Safety - /// `row` must be within the range `[0, self.len())`. - #[inline] - #[cfg(feature = "track_change_detection")] - 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()) - } - - /// Clears the column, removing all values. - /// - /// Note that this function has no effect on the allocated capacity of the [`Column`]> - pub fn clear(&mut self) { - self.data.clear(); - self.added_ticks.clear(); - self.changed_ticks.clear(); - #[cfg(feature = "track_change_detection")] - self.changed_by.clear(); - } - - #[inline] - pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { - for component_ticks in &mut self.added_ticks { - component_ticks.get_mut().check_tick(change_tick); - } - for component_ticks in &mut self.changed_ticks { - component_ticks.get_mut().check_tick(change_tick); - } - } -} - -/// A builder type for constructing [`Table`]s. -/// -/// - Use [`with_capacity`] to initialize the builder. -/// - Repeatedly call [`add_column`] to add columns for components. -/// - Finalize with [`build`] to get the constructed [`Table`]. -/// -/// [`with_capacity`]: Self::with_capacity -/// [`add_column`]: Self::add_column -/// [`build`]: Self::build -pub(crate) struct TableBuilder { - columns: SparseSet, - capacity: usize, -} - -impl TableBuilder { - /// Creates a blank [`Table`], allocating space for `column_capacity` columns - /// with the capacity to hold `capacity` entities worth of components each. - pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self { - Self { - columns: SparseSet::with_capacity(column_capacity), - capacity, - } - } - - #[must_use] - pub fn add_column(mut self, component_info: &ComponentInfo) -> Self { - self.columns.insert( - component_info.id(), - Column::with_capacity(component_info, self.capacity), - ); - self - } - - #[must_use] - pub fn build(self) -> Table { - Table { - columns: self.columns.into_immutable(), - entities: Vec::with_capacity(self.capacity), - } - } -} - -/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities -/// in a [`World`]. -/// -/// Conceptually, a `Table` can be thought of as an `HashMap`, where -/// each [`Column`] is a type-erased `Vec`. Each row corresponds to a single entity -/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same -/// entity). Fetching components from a table involves fetching the associated column for a -/// component type (via its [`ComponentId`]), then fetching the entity's row within that column. -/// -/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays -/// [`Component`]: crate::component::Component -/// [`World`]: crate::world::World -pub struct Table { - columns: ImmutableSparseSet, - entities: Vec, -} - -impl Table { - /// Fetches a read-only slice of the entities stored within the [`Table`]. - #[inline] - pub fn entities(&self) -> &[Entity] { - &self.entities - } - - /// Removes the entity at the given row and returns the entity swapped in to replace it (if an - /// entity was swapped in) - /// - /// # Safety - /// `row` must be in-bounds - pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option { - for column in self.columns.values_mut() { - column.swap_remove_unchecked(row); - } - let is_last = row.as_usize() == self.entities.len() - 1; - self.entities.swap_remove(row.as_usize()); - if is_last { - None - } else { - Some(self.entities[row.as_usize()]) - } - } - - /// Moves the `row` column values to `new_table`, for the columns shared between both tables. - /// Returns the index of the new row in `new_table` and the entity in this table swapped in - /// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is - /// the caller's responsibility to drop them. Failure to do so may result in resources not - /// being released (i.e. files handles not being released, memory leaks, etc.) - /// - /// # Safety - /// Row must be in-bounds - pub(crate) unsafe fn move_to_and_forget_missing_unchecked( - &mut self, - row: TableRow, - new_table: &mut Table, - ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); - let is_last = row.as_usize() == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); - for (component_id, column) in self.columns.iter_mut() { - if let Some(new_column) = new_table.get_column_mut(*component_id) { - new_column.initialize_from_unchecked(column, row, new_row); - } else { - // It's the caller's responsibility to drop these cases. - let (_, _, _) = column.swap_remove_and_forget_unchecked(row); - } - } - TableMoveResult { - new_row, - swapped_entity: if is_last { - None - } else { - Some(self.entities[row.as_usize()]) - }, - } - } - - /// Moves the `row` column values to `new_table`, for the columns shared between both tables. - /// Returns the index of the new row in `new_table` and the entity in this table swapped in - /// to replace it (if an entity was swapped in). - /// - /// # Safety - /// row must be in-bounds - pub(crate) unsafe fn move_to_and_drop_missing_unchecked( - &mut self, - row: TableRow, - new_table: &mut Table, - ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); - let is_last = row.as_usize() == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); - for (component_id, column) in self.columns.iter_mut() { - if let Some(new_column) = new_table.get_column_mut(*component_id) { - new_column.initialize_from_unchecked(column, row, new_row); - } else { - column.swap_remove_unchecked(row); - } - } - TableMoveResult { - new_row, - swapped_entity: if is_last { - None - } else { - Some(self.entities[row.as_usize()]) - }, - } - } - - /// Moves the `row` column values to `new_table`, for the columns shared between both tables. - /// Returns the index of the new row in `new_table` and the entity in this table swapped in - /// to replace it (if an entity was swapped in). - /// - /// # Safety - /// `row` must be in-bounds. `new_table` must contain every component this table has - pub(crate) unsafe fn move_to_superset_unchecked( - &mut self, - row: TableRow, - new_table: &mut Table, - ) -> TableMoveResult { - debug_assert!(row.as_usize() < self.entity_count()); - let is_last = row.as_usize() == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); - for (component_id, column) in self.columns.iter_mut() { - new_table - .get_column_mut(*component_id) - .debug_checked_unwrap() - .initialize_from_unchecked(column, row, new_row); - } - TableMoveResult { - new_row, - swapped_entity: if is_last { - None - } else { - Some(self.entities[row.as_usize()]) - }, - } - } - - /// Fetches a read-only reference to the [`Column`] for a given [`Component`] within the - /// table. - /// - /// Returns `None` if the corresponding component does not belong to the table. - /// - /// [`Component`]: crate::component::Component - #[inline] - pub fn get_column(&self, component_id: ComponentId) -> Option<&Column> { - self.columns.get(component_id) - } - - /// Fetches a mutable reference to the [`Column`] for a given [`Component`] within the - /// table. - /// - /// Returns `None` if the corresponding component does not belong to the table. - /// - /// [`Component`]: crate::component::Component - #[inline] - pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut Column> { - self.columns.get_mut(component_id) - } - - /// Checks if the table contains a [`Column`] for a given [`Component`]. - /// - /// Returns `true` if the column is present, `false` otherwise. - /// - /// [`Component`]: crate::component::Component - #[inline] - pub fn has_column(&self, component_id: ComponentId) -> bool { - self.columns.contains(component_id) - } - - /// Reserves `additional` elements worth of capacity within the table. - pub(crate) fn reserve(&mut self, additional: usize) { - if self.entities.capacity() - self.entities.len() < additional { - self.entities.reserve(additional); - - // use entities vector capacity as driving capacity for all related allocations - let new_capacity = self.entities.capacity(); - - for column in self.columns.values_mut() { - column.reserve_exact(new_capacity - column.len()); - } - } - } - - /// Allocates space for a new entity - /// - /// # Safety - /// the allocated row must be written to immediately with valid values in each column - pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow { - self.reserve(1); - let index = self.entities.len(); - self.entities.push(entity); - for column in self.columns.values_mut() { - column.data.set_len(self.entities.len()); - column.added_ticks.push(UnsafeCell::new(Tick::new(0))); - column.changed_ticks.push(UnsafeCell::new(Tick::new(0))); - #[cfg(feature = "track_change_detection")] - column.changed_by.push(UnsafeCell::new(Location::caller())); - } - TableRow::from_usize(index) - } - - /// Gets the number of entities currently being stored in the table. - #[inline] - pub fn entity_count(&self) -> usize { - self.entities.len() - } - - /// Gets the number of components being stored in the table. - #[inline] - pub fn component_count(&self) -> usize { - self.columns.len() - } - - /// Gets the maximum number of entities the table can currently store - /// without reallocating the underlying memory. - #[inline] - pub fn entity_capacity(&self) -> usize { - self.entities.capacity() - } - - /// Checks if the [`Table`] is empty or not. - /// - /// Returns `true` if the table contains no entities, `false` otherwise. - #[inline] - pub fn is_empty(&self) -> bool { - self.entities.is_empty() - } - - pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { - for column in self.columns.values_mut() { - column.check_change_ticks(change_tick); - } - } - - /// Iterates over the [`Column`]s of the [`Table`]. - pub fn iter(&self) -> impl Iterator { - self.columns.values() - } - - /// Clears all of the stored components in the [`Table`]. - pub(crate) fn clear(&mut self) { - self.entities.clear(); - for column in self.columns.values_mut() { - column.clear(); - } - } -} - -/// A collection of [`Table`] storages, indexed by [`TableId`] -/// -/// Can be accessed via [`Storages`](crate::storage::Storages) -pub struct Tables { - tables: Vec, - table_ids: HashMap, TableId>, -} - -impl Default for Tables { - fn default() -> Self { - let empty_table = TableBuilder::with_capacity(0, 0).build(); - Tables { - tables: vec![empty_table], - table_ids: HashMap::default(), - } - } -} - -pub(crate) struct TableMoveResult { - pub swapped_entity: Option, - pub new_row: TableRow, -} - -impl Tables { - /// Returns the number of [`Table`]s this collection contains - #[inline] - pub fn len(&self) -> usize { - self.tables.len() - } - - /// Returns true if this collection contains no [`Table`]s - #[inline] - pub fn is_empty(&self) -> bool { - self.tables.is_empty() - } - - /// Fetches a [`Table`] by its [`TableId`]. - /// - /// Returns `None` if `id` is invalid. - #[inline] - pub fn get(&self, id: TableId) -> Option<&Table> { - self.tables.get(id.as_usize()) - } - - /// Fetches mutable references to two different [`Table`]s. - /// - /// # Panics - /// - /// Panics if `a` and `b` are equal. - #[inline] - pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) { - if a.as_usize() > b.as_usize() { - let (b_slice, a_slice) = self.tables.split_at_mut(a.as_usize()); - (&mut a_slice[0], &mut b_slice[b.as_usize()]) - } else { - let (a_slice, b_slice) = self.tables.split_at_mut(b.as_usize()); - (&mut a_slice[a.as_usize()], &mut b_slice[0]) - } - } - - /// Attempts to fetch a table based on the provided components, - /// creating and returning a new [`Table`] if one did not already exist. - /// - /// # Safety - /// `component_ids` must contain components that exist in `components` - pub(crate) unsafe fn get_id_or_insert( - &mut self, - component_ids: &[ComponentId], - components: &Components, - ) -> TableId { - let tables = &mut self.tables; - let (_key, value) = self - .table_ids - .raw_entry_mut() - .from_key(component_ids) - .or_insert_with(|| { - let mut table = TableBuilder::with_capacity(0, component_ids.len()); - for component_id in component_ids { - table = table.add_column(components.get_info_unchecked(*component_id)); - } - tables.push(table.build()); - (component_ids.into(), TableId::from_usize(tables.len() - 1)) - }); - - *value - } - - /// Iterates through all of the tables stored within in [`TableId`] order. - pub fn iter(&self) -> std::slice::Iter<'_, Table> { - self.tables.iter() - } - - /// Clears all data from all [`Table`]s stored within. - pub(crate) fn clear(&mut self) { - for table in &mut self.tables { - table.clear(); - } - } - - pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { - for table in &mut self.tables { - table.check_change_ticks(change_tick); - } - } -} - -impl Index for Tables { - type Output = Table; - - #[inline] - fn index(&self, index: TableId) -> &Self::Output { - &self.tables[index.as_usize()] - } -} - -impl IndexMut for Tables { - #[inline] - fn index_mut(&mut self, index: TableId) -> &mut Self::Output { - &mut self.tables[index.as_usize()] - } -} - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::component::Component; - use crate::ptr::OwningPtr; - use crate::storage::Storages; - use crate::{ - component::{Components, Tick}, - entity::Entity, - storage::{TableBuilder, TableRow}, - }; - #[cfg(feature = "track_change_detection")] - use std::panic::Location; - - #[derive(Component)] - struct W(T); - - #[test] - fn table() { - let mut components = Components::default(); - let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages); - let columns = &[component_id]; - let mut table = TableBuilder::with_capacity(0, columns.len()) - .add_column(components.get_info(component_id).unwrap()) - .build(); - let entities = (0..200).map(Entity::from_raw).collect::>(); - for entity in &entities { - // SAFETY: we allocate and immediately set data afterwards - unsafe { - let row = table.allocate(*entity); - let value: W = W(row); - OwningPtr::make(value, |value_ptr| { - table.get_column_mut(component_id).unwrap().initialize( - row, - value_ptr, - Tick::new(0), - #[cfg(feature = "track_change_detection")] - Location::caller(), - ); - }); - }; - } - - assert_eq!(table.entity_capacity(), 256); - assert_eq!(table.entity_count(), 200); - } -} diff --git a/crates/bevy_ecs/src/storage/table/column.rs b/crates/bevy_ecs/src/storage/table/column.rs new file mode 100644 index 0000000000..ccc930d552 --- /dev/null +++ b/crates/bevy_ecs/src/storage/table/column.rs @@ -0,0 +1,688 @@ +use super::*; +use crate::{ + component::TickCells, + storage::{blob_array::BlobArray, thin_array_ptr::ThinArrayPtr}, +}; +use bevy_ptr::PtrMut; + +/// Very similar to a normal [`Column`], but with the capacities and lengths cut out for performance reasons. +/// This type is used by [`Table`], because all of the capacities and lengths of the [`Table`]'s columns must match. +/// +/// Like many other low-level storage types, [`ThinColumn`] has a limited and highly unsafe +/// interface. It's highly advised to use higher level types and their safe abstractions +/// instead of working directly with [`ThinColumn`]. +pub struct ThinColumn { + pub(super) data: BlobArray, + pub(super) added_ticks: ThinArrayPtr>, + pub(super) changed_ticks: ThinArrayPtr>, + #[cfg(feature = "track_change_detection")] + pub(super) changed_by: ThinArrayPtr>>, +} + +impl ThinColumn { + /// Create a new [`ThinColumn`] with the given `capacity`. + pub fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self { + Self { + // SAFETY: The components stored in this columns will match the information in `component_info` + data: unsafe { + BlobArray::with_capacity(component_info.layout(), component_info.drop(), capacity) + }, + added_ticks: ThinArrayPtr::with_capacity(capacity), + changed_ticks: ThinArrayPtr::with_capacity(capacity), + #[cfg(feature = "track_change_detection")] + changed_by: ThinArrayPtr::with_capacity(capacity), + } + } + + /// Swap-remove and drop the removed element, but the component at `row` must not be the last element. + /// + /// # Safety + /// - `row.as_usize()` < `len` + /// - `last_element_index` = `len - 1` + /// - `last_element_index` != `row.as_usize()` + /// - The caller should update the `len` to `len - 1`, or immediately initialize another element in the `last_element_index` + pub(crate) unsafe fn swap_remove_and_drop_unchecked_nonoverlapping( + &mut self, + last_element_index: usize, + row: TableRow, + ) { + self.data + .swap_remove_and_drop_unchecked_nonoverlapping(row.as_usize(), last_element_index); + self.added_ticks + .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_change_detection")] + self.changed_by + .swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index); + } + + /// Swap-remove and drop the removed element. + /// + /// # Safety + /// - `last_element_index` must be the index of the last element—stored in the highest place in memory. + /// - `row.as_usize()` <= `last_element_index` + /// - The caller should update the their saved length to reflect the change (decrement it by 1). + pub(crate) unsafe fn swap_remove_and_drop_unchecked( + &mut self, + last_element_index: usize, + row: TableRow, + ) { + self.data + .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + self.added_ticks + .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_change_detection")] + self.changed_by + .swap_remove_and_drop_unchecked(row.as_usize(), last_element_index); + } + + /// Swap-remove and forget the removed element. + /// + /// # Safety + /// - `last_element_index` must be the index of the last element—stored in the highest place in memory. + /// - `row.as_usize()` <= `last_element_index` + /// - The caller should update the their saved length to reflect the change (decrement it by 1). + pub(crate) unsafe fn swap_remove_and_forget_unchecked( + &mut self, + last_element_index: usize, + row: TableRow, + ) { + let _ = self + .data + .swap_remove_unchecked(row.as_usize(), last_element_index); + self.added_ticks + .swap_remove_unchecked(row.as_usize(), last_element_index); + self.changed_ticks + .swap_remove_unchecked(row.as_usize(), last_element_index); + #[cfg(feature = "track_change_detection")] + self.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`] + /// + /// # Safety + /// - `current_capacity` must be the current capacity of this column (the capacity of `self.data`, `self.added_ticks`, `self.changed_tick`) + /// - The caller should make sure their saved `capacity` value is updated to `new_capacity` after this operation. + pub(crate) unsafe fn realloc( + &mut self, + current_capacity: NonZeroUsize, + new_capacity: NonZeroUsize, + ) { + 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_change_detection")] + self.changed_by.realloc(current_capacity, new_capacity); + } + + /// Call [`alloc`](std::alloc::alloc) to allocate memory for this [`ThinColumn`] + /// The caller should make sure their saved `capacity` value is updated to `new_capacity` after this operation. + pub(crate) fn alloc(&mut self, new_capacity: NonZeroUsize) { + self.data.alloc(new_capacity); + self.added_ticks.alloc(new_capacity); + self.changed_ticks.alloc(new_capacity); + #[cfg(feature = "track_change_detection")] + self.changed_by.alloc(new_capacity); + } + + /// Writes component data to the column at the given row. + /// Assumes the slot is uninitialized, drop is not called. + /// To overwrite existing initialized value, use [`Self::replace`] instead. + /// + /// # Safety + /// - `row.as_usize()` must be in bounds. + /// - `comp_ptr` holds a component that matches the `component_id` + #[inline] + pub(crate) unsafe fn initialize( + &mut self, + row: TableRow, + data: OwningPtr<'_>, + tick: Tick, + #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + ) { + self.data.initialize_unchecked(row.as_usize(), data); + *self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick; + *self + .changed_ticks + .get_unchecked_mut(row.as_usize()) + .get_mut() = tick; + #[cfg(feature = "track_change_detection")] + { + *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; + } + } + + /// Writes component data to the column at given row. Assumes the slot is initialized, drops the previous value. + /// + /// # Safety + /// - `row.as_usize()` must be in bounds. + /// - `data` holds a component that matches the `component_id` + #[inline] + pub(crate) unsafe fn replace( + &mut self, + row: TableRow, + data: OwningPtr<'_>, + change_tick: Tick, + #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + ) { + self.data.replace_unchecked(row.as_usize(), data); + *self + .changed_ticks + .get_unchecked_mut(row.as_usize()) + .get_mut() = change_tick; + #[cfg(feature = "track_change_detection")] + { + *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; + } + } + + /// Removes the element from `other` at `src_row` and inserts it + /// into the current column to initialize the values at `dst_row`. + /// Does not do any bounds checking. + /// + /// # Safety + /// - `other` must have the same data layout as `self` + /// - `src_row` must be in bounds for `other` + /// - `dst_row` must be in bounds for `self` + /// - `other[src_row]` must be initialized to a valid value. + /// - `self[dst_row]` must not be initialized yet. + #[inline] + pub(crate) unsafe fn initialize_from_unchecked( + &mut self, + other: &mut ThinColumn, + other_last_element_index: usize, + src_row: TableRow, + dst_row: TableRow, + ) { + debug_assert!(self.data.layout() == other.data.layout()); + // Init the data + let src_val = other + .data + .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); + self.data.initialize_unchecked(dst_row.as_usize(), src_val); + // Init added_ticks + let added_tick = other + .added_ticks + .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); + self.added_ticks + .initialize_unchecked(dst_row.as_usize(), added_tick); + // Init changed_ticks + let changed_tick = other + .changed_ticks + .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_change_detection")] + let changed_by = other + .changed_by + .swap_remove_unchecked(src_row.as_usize(), other_last_element_index); + #[cfg(feature = "track_change_detection")] + self.changed_by + .initialize_unchecked(dst_row.as_usize(), changed_by); + } + + /// Call [`Tick::check_tick`] on all of the ticks stored in this column. + /// + /// # Safety + /// `len` is the actual length of this column + #[inline] + pub(crate) unsafe fn check_change_ticks(&mut self, len: usize, change_tick: Tick) { + for i in 0..len { + // SAFETY: + // - `i` < `len` + // we have a mutable reference to `self` + unsafe { self.added_ticks.get_unchecked_mut(i) } + .get_mut() + .check_tick(change_tick); + // SAFETY: + // - `i` < `len` + // we have a mutable reference to `self` + unsafe { self.changed_ticks.get_unchecked_mut(i) } + .get_mut() + .check_tick(change_tick); + } + } + + /// Clear all the components from this column. + /// + /// # Safety + /// - `len` must match the actual length of the column + /// - The caller must not use the elements this column's data until [`initializing`](Self::initialize) it again (set `len` to 0). + pub(crate) unsafe fn clear(&mut self, len: usize) { + self.added_ticks.clear_elements(len); + self.changed_ticks.clear_elements(len); + self.data.clear(len); + #[cfg(feature = "track_change_detection")] + self.changed_by.clear_elements(len); + } + + /// Because this method needs parameters, it can't be the implementation of the `Drop` trait. + /// The owner of this [`ThinColumn`] must call this method with the correct information. + /// + /// # Safety + /// - `len` is indeed the length of the column + /// - `cap` is indeed the capacity of the column + /// - the data stored in `self` will never be used again + pub(crate) unsafe fn drop(&mut self, cap: usize, len: usize) { + self.added_ticks.drop(cap, len); + self.changed_ticks.drop(cap, len); + self.data.drop(cap, len); + #[cfg(feature = "track_change_detection")] + self.changed_by.drop(cap, len); + } + + /// Drops the last component in this column. + /// + /// # Safety + /// - `last_element_index` is indeed the index of the last element + /// - the data stored in `last_element_index` will never be used unless properly initialized again. + pub(crate) unsafe fn drop_last_component(&mut self, last_element_index: usize) { + std::ptr::drop_in_place(self.added_ticks.get_unchecked_raw(last_element_index)); + std::ptr::drop_in_place(self.changed_ticks.get_unchecked_raw(last_element_index)); + #[cfg(feature = "track_change_detection")] + std::ptr::drop_in_place(self.changed_by.get_unchecked_raw(last_element_index)); + self.data.drop_last_element(last_element_index); + } + + /// Get a slice to the data stored in this [`ThinColumn`]. + /// + /// # Safety + /// - `T` must match the type of data that's stored in this [`ThinColumn`] + /// - `len` must match the actual length of this column (number of elements stored) + pub unsafe fn get_data_slice(&self, len: usize) -> &[UnsafeCell] { + self.data.get_sub_slice(len) + } + + /// Get a slice to the added [`ticks`](Tick) in this [`ThinColumn`]. + /// + /// # Safety + /// - `len` must match the actual length of this column (number of elements stored) + pub unsafe fn get_added_ticks_slice(&self, len: usize) -> &[UnsafeCell] { + self.added_ticks.as_slice(len) + } + + /// Get a slice to the changed [`ticks`](Tick) in this [`ThinColumn`]. + /// + /// # Safety + /// - `len` must match the actual length of this column (number of elements stored) + pub unsafe fn get_changed_ticks_slice(&self, len: usize) -> &[UnsafeCell] { + self.changed_ticks.as_slice(len) + } + + /// Get a slice to the calling locations that last changed each value in this [`ThinColumn`] + /// + /// # Safety + /// - `len` must match the actual length of this column (number of elements stored) + #[cfg(feature = "track_change_detection")] + pub unsafe fn get_changed_by_slice( + &self, + len: usize, + ) -> &[UnsafeCell<&'static Location<'static>>] { + self.changed_by.as_slice(len) + } +} + +/// A type-erased contiguous container for data of a homogeneous type. +/// +/// Conceptually, a [`Column`] is very similar to a type-erased `Vec`. +/// It also stores the change detection ticks for its components, kept in two separate +/// contiguous buffers internally. An element shares its data across these buffers by using the +/// same index (i.e. the entity at row 3 has it's data at index 3 and its change detection ticks at index 3). +/// +/// Like many other low-level storage types, [`Column`] has a limited and highly unsafe +/// interface. It's highly advised to use higher level types and their safe abstractions +/// instead of working directly with [`Column`]. +#[derive(Debug)] +pub struct Column { + pub(super) data: BlobVec, + pub(super) added_ticks: Vec>, + pub(super) changed_ticks: Vec>, + #[cfg(feature = "track_change_detection")] + changed_by: Vec>>, +} + +impl Column { + /// Constructs a new [`Column`], configured with a component's layout and an initial `capacity`. + #[inline] + pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self { + Column { + // SAFETY: component_info.drop() is valid for the types that will be inserted. + data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, + added_ticks: Vec::with_capacity(capacity), + changed_ticks: Vec::with_capacity(capacity), + #[cfg(feature = "track_change_detection")] + changed_by: Vec::with_capacity(capacity), + } + } + + /// Fetches the [`Layout`] for the underlying type. + #[inline] + pub fn item_layout(&self) -> Layout { + self.data.layout() + } + + /// Writes component data to the column at given row. + /// Assumes the slot is initialized, calls drop. + /// + /// # Safety + /// Assumes data has already been allocated for the given row. + #[inline] + pub(crate) unsafe fn replace( + &mut self, + row: TableRow, + data: OwningPtr<'_>, + change_tick: Tick, + #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + ) { + debug_assert!(row.as_usize() < self.len()); + self.data.replace_unchecked(row.as_usize(), data); + *self + .changed_ticks + .get_unchecked_mut(row.as_usize()) + .get_mut() = change_tick; + #[cfg(feature = "track_change_detection")] + { + *self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller; + } + } + + /// Gets the current number of elements stored in the column. + #[inline] + pub fn len(&self) -> usize { + self.data.len() + } + + /// Checks if the column is empty. Returns `true` if there are no elements, `false` otherwise. + #[inline] + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Removes an element from the [`Column`]. + /// + /// - The value will be dropped if it implements [`Drop`]. + /// - This does not preserve ordering, but is O(1). + /// - This does not do any bounds checking. + /// - The element is replaced with the last element in the [`Column`]. + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + /// + #[inline] + pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { + self.data.swap_remove_and_drop_unchecked(row.as_usize()); + self.added_ticks.swap_remove(row.as_usize()); + self.changed_ticks.swap_remove(row.as_usize()); + #[cfg(feature = "track_change_detection")] + self.changed_by.swap_remove(row.as_usize()); + } + + /// Removes an element from the [`Column`] and returns it and its change detection ticks. + /// This does not preserve ordering, but is O(1) and does not do any bounds checking. + /// + /// The element is replaced with the last element in the [`Column`]. + /// + /// It's the caller's responsibility to ensure that the removed value is dropped or used. + /// Failure to do so may result in resources not being released (i.e. files handles not being + /// released, memory leaks, etc.) + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + #[inline] + #[must_use = "The returned pointer should be used to dropped the removed component"] + pub(crate) unsafe fn swap_remove_and_forget_unchecked( + &mut self, + row: TableRow, + ) -> (OwningPtr<'_>, ComponentTicks, MaybeLocation) { + let data = self.data.swap_remove_and_forget_unchecked(row.as_usize()); + let added = self.added_ticks.swap_remove(row.as_usize()).into_inner(); + let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner(); + #[cfg(feature = "track_change_detection")] + let caller = self.changed_by.swap_remove(row.as_usize()).into_inner(); + #[cfg(not(feature = "track_change_detection"))] + let caller = (); + (data, ComponentTicks { added, changed }, caller) + } + + /// Pushes a new value onto the end of the [`Column`]. + /// + /// # Safety + /// `ptr` must point to valid data of this column's component type + pub(crate) unsafe fn push( + &mut self, + ptr: OwningPtr<'_>, + ticks: ComponentTicks, + #[cfg(feature = "track_change_detection")] caller: &'static Location<'static>, + ) { + self.data.push(ptr); + self.added_ticks.push(UnsafeCell::new(ticks.added)); + self.changed_ticks.push(UnsafeCell::new(ticks.changed)); + #[cfg(feature = "track_change_detection")] + self.changed_by.push(UnsafeCell::new(caller)); + } + + /// Fetches the data pointer to the first element of the [`Column`]. + /// + /// The pointer is type erased, so using this function to fetch anything + /// other than the first element will require computing the offset using + /// [`Column::item_layout`]. + #[inline] + pub fn get_data_ptr(&self) -> Ptr<'_> { + self.data.get_ptr() + } + + /// Fetches the slice to the [`Column`]'s data cast to a given type. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + /// + /// # Safety + /// The type `T` must be the type of the items in this column. + pub unsafe fn get_data_slice(&self) -> &[UnsafeCell] { + self.data.get_slice() + } + + /// Fetches the slice to the [`Column`]'s "added" change detection ticks. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + #[inline] + pub fn get_added_ticks_slice(&self) -> &[UnsafeCell] { + &self.added_ticks + } + + /// Fetches the slice to the [`Column`]'s "changed" change detection ticks. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + #[inline] + pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell] { + &self.changed_ticks + } + + /// Fetches a reference to the data and change detection ticks at `row`. + /// + /// Returns `None` if `row` is out of bounds. + #[inline] + pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> { + (row.as_usize() < self.data.len()) + // SAFETY: The row is length checked before fetching the pointer. This is being + // accessed through a read-only reference to the column. + .then(|| unsafe { + ( + self.data.get_unchecked(row.as_usize()), + TickCells { + added: self.added_ticks.get_unchecked(row.as_usize()), + changed: self.changed_ticks.get_unchecked(row.as_usize()), + }, + ) + }) + } + + /// Fetches a read-only reference to the data at `row`. + /// + /// Returns `None` if `row` is out of bounds. + #[inline] + pub fn get_data(&self, row: TableRow) -> Option> { + (row.as_usize() < self.data.len()).then(|| { + // SAFETY: The row is length checked before fetching the pointer. This is being + // accessed through a read-only reference to the column. + unsafe { self.data.get_unchecked(row.as_usize()) } + }) + } + + /// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not + /// do any bounds checking. + /// + /// # Safety + /// - `row` must be within the range `[0, self.len())`. + /// - no other mutable reference to the data of the same row can exist at the same time + #[inline] + pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { + debug_assert!(row.as_usize() < self.data.len()); + self.data.get_unchecked(row.as_usize()) + } + + /// Fetches a mutable reference to the data at `row`. + /// + /// Returns `None` if `row` is out of bounds. + #[inline] + pub fn get_data_mut(&mut self, row: TableRow) -> Option> { + (row.as_usize() < self.data.len()).then(|| { + // SAFETY: The row is length checked before fetching the pointer. This is being + // accessed through an exclusive reference to the column. + unsafe { self.data.get_unchecked_mut(row.as_usize()) } + }) + } + + /// Fetches the "added" change detection tick for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + #[inline] + pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell> { + self.added_ticks.get(row.as_usize()) + } + + /// Fetches the "changed" change detection tick for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + #[inline] + pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell> { + self.changed_ticks.get(row.as_usize()) + } + + /// Fetches the change detection ticks for the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + #[inline] + pub fn get_ticks(&self, row: TableRow) -> Option { + if row.as_usize() < self.data.len() { + // SAFETY: The size of the column has already been checked. + Some(unsafe { self.get_ticks_unchecked(row) }) + } else { + None + } + } + + /// Fetches the "added" change detection tick for the value at `row`. Unlike [`Column::get_added_tick`] + /// this function does not do any bounds checking. + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + #[inline] + pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { + debug_assert!(row.as_usize() < self.added_ticks.len()); + self.added_ticks.get_unchecked(row.as_usize()) + } + + /// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`] + /// this function does not do any bounds checking. + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + #[inline] + pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell { + debug_assert!(row.as_usize() < self.changed_ticks.len()); + self.changed_ticks.get_unchecked(row.as_usize()) + } + + /// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`] + /// this function does not do any bounds checking. + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + #[inline] + pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { + debug_assert!(row.as_usize() < self.added_ticks.len()); + debug_assert!(row.as_usize() < self.changed_ticks.len()); + ComponentTicks { + added: self.added_ticks.get_unchecked(row.as_usize()).read(), + changed: self.changed_ticks.get_unchecked(row.as_usize()).read(), + } + } + + /// Clears the column, removing all values. + /// + /// Note that this function has no effect on the allocated capacity of the [`Column`]> + pub fn clear(&mut self) { + self.data.clear(); + self.added_ticks.clear(); + self.changed_ticks.clear(); + #[cfg(feature = "track_change_detection")] + self.changed_by.clear(); + } + + #[inline] + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + for component_ticks in &mut self.added_ticks { + component_ticks.get_mut().check_tick(change_tick); + } + for component_ticks in &mut self.changed_ticks { + component_ticks.get_mut().check_tick(change_tick); + } + } + + /// Fetches the calling location that last changed the value at `row`. + /// + /// Returns `None` if `row` is out of bounds. + /// + /// Note: The values stored within are [`UnsafeCell`]. + /// Users of this API must ensure that accesses to each individual element + /// adhere to the safety invariants of [`UnsafeCell`]. + #[inline] + #[cfg(feature = "track_change_detection")] + pub fn get_changed_by(&self, row: TableRow) -> Option<&UnsafeCell<&'static Location<'static>>> { + self.changed_by.get(row.as_usize()) + } + + /// Fetches the calling location that last changed the value at `row`. + /// + /// Unlike [`Column::get_changed_by`] this function does not do any bounds checking. + /// + /// # Safety + /// `row` must be within the range `[0, self.len())`. + #[inline] + #[cfg(feature = "track_change_detection")] + 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()) + } +} diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs new file mode 100644 index 0000000000..c08b49c336 --- /dev/null +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -0,0 +1,861 @@ +use crate::{ + change_detection::MaybeLocation, + component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick}, + entity::Entity, + query::DebugCheckedUnwrap, + storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, +}; +use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_utils::HashMap; +pub use column::*; +#[cfg(feature = "track_change_detection")] +use std::panic::Location; +use std::{alloc::Layout, num::NonZeroUsize}; +use std::{ + cell::UnsafeCell, + ops::{Index, IndexMut}, +}; +mod column; + +/// An opaque unique ID for a [`Table`] within a [`World`]. +/// +/// Can be used with [`Tables::get`] to fetch the corresponding +/// table. +/// +/// Each [`Archetype`] always points to a table via [`Archetype::table_id`]. +/// Multiple archetypes can point to the same table so long as the components +/// stored in the table are identical, but do not share the same sparse set +/// components. +/// +/// [`World`]: crate::world::World +/// [`Archetype`]: crate::archetype::Archetype +/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation +#[repr(transparent)] +pub struct TableId(u32); + +impl TableId { + pub(crate) const INVALID: TableId = TableId(u32::MAX); + + /// Creates a new [`TableId`]. + /// + /// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got + /// from a table of a given [`World`] or the created ID may be invalid. + /// + /// [`World`]: crate::world::World + #[inline] + pub const fn from_u32(index: u32) -> Self { + Self(index) + } + + /// Creates a new [`TableId`]. + /// + /// `index` *must* be retrieved from calling [`TableId::as_usize`] on a `TableId` you got + /// from a table of a given [`World`] or the created ID may be invalid. + /// + /// [`World`]: crate::world::World + /// + /// # Panics + /// + /// Will panic if the provided value does not fit within a [`u32`]. + #[inline] + pub const fn from_usize(index: usize) -> Self { + debug_assert!(index as u32 as usize == index); + Self(index as u32) + } + + /// Gets the underlying table index from the ID. + #[inline] + pub const fn as_u32(self) -> u32 { + self.0 + } + + /// Gets the underlying table index from the ID. + #[inline] + pub const fn as_usize(self) -> usize { + // usize is at least u32 in Bevy + self.0 as usize + } + + /// The [`TableId`] of the [`Table`] without any components. + #[inline] + pub const fn empty() -> Self { + Self(0) + } +} + +/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table. +/// +/// Values of this type are retrievable from [`Archetype::entity_table_row`] and can be +/// used alongside [`Archetype::table_id`] to fetch the exact table and row where an +/// [`Entity`]'s +/// +/// Values of this type are only valid so long as entities have not moved around. +/// Adding and removing components from an entity, or despawning it will invalidate +/// potentially any table row in the table the entity was previously stored in. Users +/// should *always* fetch the appropriate row from the entity's [`Archetype`] before +/// fetching the entity's components. +/// +/// [`Archetype`]: crate::archetype::Archetype +/// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row +/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation +#[repr(transparent)] +pub struct TableRow(u32); + +impl TableRow { + pub(crate) const INVALID: TableRow = TableRow(u32::MAX); + + /// Creates a `TableRow`. + #[inline] + pub const fn from_u32(index: u32) -> Self { + Self(index) + } + + /// Creates a `TableRow` from a [`usize`] index. + /// + /// # Panics + /// + /// Will panic if the provided value does not fit within a [`u32`]. + #[inline] + pub const fn from_usize(index: usize) -> Self { + debug_assert!(index as u32 as usize == index); + Self(index as u32) + } + + /// Gets the index of the row as a [`usize`]. + #[inline] + pub const fn as_usize(self) -> usize { + // usize is at least u32 in Bevy + self.0 as usize + } + + /// Gets the index of the row as a [`usize`]. + #[inline] + pub const fn as_u32(self) -> u32 { + self.0 + } +} + +/// A builder type for constructing [`Table`]s. +/// +/// - Use [`with_capacity`] to initialize the builder. +/// - Repeatedly call [`add_column`] to add columns for components. +/// - Finalize with [`build`] to get the constructed [`Table`]. +/// +/// [`with_capacity`]: Self::with_capacity +/// [`add_column`]: Self::add_column +/// [`build`]: Self::build +pub(crate) struct TableBuilder { + columns: SparseSet, + capacity: usize, +} + +impl TableBuilder { + /// Start building a new [`Table`] with a specified `column_capacity` (How many components per column?) and a `capacity` (How many columns?) + pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self { + Self { + columns: SparseSet::with_capacity(column_capacity), + capacity, + } + } + + /// Add a new column to the [`Table`]. Specify the component which will be stored in the [`column`](ThinColumn) using its [`ComponentId`] + #[must_use] + pub fn add_column(mut self, component_info: &ComponentInfo) -> Self { + self.columns.insert( + component_info.id(), + ThinColumn::with_capacity(component_info, self.capacity), + ); + self + } + + /// Build the [`Table`], after this operation the caller wouldn't be able to add more columns. The [`Table`] will be ready to use. + #[must_use] + pub fn build(self) -> Table { + Table { + columns: self.columns.into_immutable(), + entities: Vec::with_capacity(self.capacity), + } + } +} + +/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities +/// in a [`World`]. +/// +/// Conceptually, a `Table` can be thought of as an `HashMap`, where +/// each [`ThinColumn`] is a type-erased `Vec`. Each row corresponds to a single entity +/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same +/// entity). Fetching components from a table involves fetching the associated column for a +/// component type (via its [`ComponentId`]), then fetching the entity's row within that column. +/// +/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays +/// [`Component`]: crate::component::Component +/// [`World`]: crate::world::World +pub struct Table { + columns: ImmutableSparseSet, + entities: Vec, +} + +struct AbortOnPanic; + +impl Drop for AbortOnPanic { + fn drop(&mut self) { + // Panicking while unwinding will force an abort. + panic!("Aborting due to allocator error"); + } +} + +impl Table { + /// Fetches a read-only slice of the entities stored within the [`Table`]. + #[inline] + pub fn entities(&self) -> &[Entity] { + &self.entities + } + + /// Get the capacity of this table, in entities. + /// Note that if an allocation is in process, this might not match the actual capacity of the columns, but it should once the allocation ends. + #[inline] + pub fn capacity(&self) -> usize { + self.entities.capacity() + } + + /// Removes the entity at the given row and returns the entity swapped in to replace it (if an + /// entity was swapped in) + /// + /// # Safety + /// `row` must be in-bounds (`row.as_usize()` < `self.len()`) + pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option { + debug_assert!(row.as_usize() < self.entity_count()); + let last_element_index = self.entity_count() - 1; + if row.as_usize() != last_element_index { + // Instead of checking this condition on every `swap_remove` call, we + // check it here and use `swap_remove_nonoverlapping`. + for col in self.columns.values_mut() { + // SAFETY: + // - `row` < `len` + // - `last_element_index` = `len` - 1 + // - `row` != `last_element_index` + // - the `len` is kept within `self.entities`, it will update accordingly. + unsafe { + col.swap_remove_and_drop_unchecked_nonoverlapping(last_element_index, row); + }; + } + } else { + // If `row.as_usize()` == `last_element_index` than there's no point in removing the component + // at `row`, but we still need to drop it. + for col in self.columns.values_mut() { + col.drop_last_component(last_element_index); + } + } + let is_last = row.as_usize() == last_element_index; + self.entities.swap_remove(row.as_usize()); + if is_last { + None + } else { + Some(self.entities[row.as_usize()]) + } + } + + /// Moves the `row` column values to `new_table`, for the columns shared between both tables. + /// Returns the index of the new row in `new_table` and the entity in this table swapped in + /// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is + /// the caller's responsibility to drop them. Failure to do so may result in resources not + /// being released (i.e. files handles not being released, memory leaks, etc.) + /// + /// # Safety + /// - `row` must be in-bounds + pub(crate) unsafe fn move_to_and_forget_missing_unchecked( + &mut self, + row: TableRow, + new_table: &mut Table, + ) -> TableMoveResult { + debug_assert!(row.as_usize() < self.entity_count()); + let last_element_index = self.entity_count() - 1; + let is_last = row.as_usize() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + for (component_id, column) in self.columns.iter_mut() { + if let Some(new_column) = new_table.get_column_mut(*component_id) { + new_column.initialize_from_unchecked(column, last_element_index, row, new_row); + } else { + // It's the caller's responsibility to drop these cases. + column.swap_remove_and_forget_unchecked(last_element_index, row); + } + } + TableMoveResult { + new_row, + swapped_entity: if is_last { + None + } else { + Some(self.entities[row.as_usize()]) + }, + } + } + + /// Moves the `row` column values to `new_table`, for the columns shared between both tables. + /// Returns the index of the new row in `new_table` and the entity in this table swapped in + /// to replace it (if an entity was swapped in). + /// + /// # Safety + /// row must be in-bounds + pub(crate) unsafe fn move_to_and_drop_missing_unchecked( + &mut self, + row: TableRow, + new_table: &mut Table, + ) -> TableMoveResult { + debug_assert!(row.as_usize() < self.entity_count()); + let last_element_index = self.entity_count() - 1; + let is_last = row.as_usize() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + for (component_id, column) in self.columns.iter_mut() { + if let Some(new_column) = new_table.get_column_mut(*component_id) { + new_column.initialize_from_unchecked(column, last_element_index, row, new_row); + } else { + column.swap_remove_and_drop_unchecked(last_element_index, row); + } + } + TableMoveResult { + new_row, + swapped_entity: if is_last { + None + } else { + Some(self.entities[row.as_usize()]) + }, + } + } + + /// Moves the `row` column values to `new_table`, for the columns shared between both tables. + /// Returns the index of the new row in `new_table` and the entity in this table swapped in + /// to replace it (if an entity was swapped in). + /// + /// # Safety + /// - `row` must be in-bounds + /// - `new_table` must contain every component this table has + pub(crate) unsafe fn move_to_superset_unchecked( + &mut self, + row: TableRow, + new_table: &mut Table, + ) -> TableMoveResult { + debug_assert!(row.as_usize() < self.entity_count()); + let last_element_index = self.entity_count() - 1; + let is_last = row.as_usize() == last_element_index; + let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize())); + for (component_id, column) in self.columns.iter_mut() { + new_table + .get_column_mut(*component_id) + .debug_checked_unwrap() + .initialize_from_unchecked(column, last_element_index, row, new_row); + } + TableMoveResult { + new_row, + swapped_entity: if is_last { + None + } else { + Some(self.entities[row.as_usize()]) + }, + } + } + + /// Get the data of the column matching `component_id` as a slice. + /// + /// # Safety + /// `row.as_usize()` < `self.len()` + /// - `T` must match the `component_id` + pub unsafe fn get_data_slice_for( + &self, + component_id: ComponentId, + ) -> Option<&[UnsafeCell]> { + self.get_column(component_id) + .map(|col| col.get_data_slice(self.entity_count())) + } + + /// Get the added ticks of the column matching `component_id` as a slice. + pub fn get_added_ticks_slice_for( + &self, + component_id: ComponentId, + ) -> Option<&[UnsafeCell]> { + self.get_column(component_id) + // SAFETY: `self.len()` is guaranteed to be the len of the ticks array + .map(|col| unsafe { col.get_added_ticks_slice(self.entity_count()) }) + } + + /// Get the changed ticks of the column matching `component_id` as a slice. + pub fn get_changed_ticks_slice_for( + &self, + component_id: ComponentId, + ) -> Option<&[UnsafeCell]> { + self.get_column(component_id) + // SAFETY: `self.len()` is guaranteed to be the len of the ticks array + .map(|col| unsafe { col.get_changed_ticks_slice(self.entity_count()) }) + } + + /// Fetches the calling locations that last changed the each component + #[cfg(feature = "track_change_detection")] + 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()) }) + } + + /// Get the specific [`change tick`](Tick) of the component matching `component_id` in `row`. + pub fn get_changed_tick( + &self, + component_id: ComponentId, + row: TableRow, + ) -> Option<&UnsafeCell> { + (row.as_usize() < self.entity_count()).then_some( + // SAFETY: `row.as_usize()` < `len` + unsafe { + self.get_column(component_id)? + .changed_ticks + .get_unchecked(row.as_usize()) + }, + ) + } + + /// Get the specific [`added tick`](Tick) of the component matching `component_id` in `row`. + pub fn get_added_tick( + &self, + component_id: ComponentId, + row: TableRow, + ) -> Option<&UnsafeCell> { + (row.as_usize() < self.entity_count()).then_some( + // SAFETY: `row.as_usize()` < `len` + unsafe { + self.get_column(component_id)? + .added_ticks + .get_unchecked(row.as_usize()) + }, + ) + } + + /// Get the specific calling location that changed the component matching `component_id` in `row` + #[cfg(feature = "track_change_detection")] + 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()) + }, + ) + } + + /// Get the [`ComponentTicks`] of the component matching `component_id` in `row`. + /// + /// # Safety + /// - `row.as_usize()` < `self.len()` + pub unsafe fn get_ticks_unchecked( + &self, + component_id: ComponentId, + row: TableRow, + ) -> Option { + self.get_column(component_id).map(|col| ComponentTicks { + added: col.added_ticks.get_unchecked(row.as_usize()).read(), + changed: col.changed_ticks.get_unchecked(row.as_usize()).read(), + }) + } + + /// Fetches a read-only reference to the [`ThinColumn`] for a given [`Component`] within the table. + /// + /// Returns `None` if the corresponding component does not belong to the table. + /// + /// [`Component`]: crate::component::Component + #[inline] + pub fn get_column(&self, component_id: ComponentId) -> Option<&ThinColumn> { + self.columns.get(component_id) + } + + /// Fetches a mutable reference to the [`ThinColumn`] for a given [`Component`] within the + /// table. + /// + /// Returns `None` if the corresponding component does not belong to the table. + /// + /// [`Component`]: crate::component::Component + #[inline] + pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut ThinColumn> { + self.columns.get_mut(component_id) + } + + /// Checks if the table contains a [`ThinColumn`] for a given [`Component`]. + /// + /// Returns `true` if the column is present, `false` otherwise. + /// + /// [`Component`]: crate::component::Component + #[inline] + pub fn has_column(&self, component_id: ComponentId) -> bool { + self.columns.contains(component_id) + } + + /// Reserves `additional` elements worth of capacity within the table. + pub(crate) fn reserve(&mut self, additional: usize) { + if self.capacity() - self.entity_count() < additional { + let column_cap = self.capacity(); + self.entities.reserve(additional); + + // use entities vector capacity as driving capacity for all related allocations + let new_capacity = self.entities.capacity(); + + if column_cap == 0 { + // SAFETY: the current capacity is 0 + unsafe { self.alloc_columns(NonZeroUsize::new_unchecked(new_capacity)) }; + } else { + // SAFETY: + // - `column_cap` is indeed the columns' capacity + unsafe { + self.realloc_columns( + NonZeroUsize::new_unchecked(column_cap), + NonZeroUsize::new_unchecked(new_capacity), + ); + }; + } + } + } + + /// Allocate memory for the columns in the [`Table`] + /// + /// The current capacity of the columns should be 0, if it's not 0, then the previous data will be overwritten and leaked. + fn alloc_columns(&mut self, new_capacity: NonZeroUsize) { + // If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB. + // To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be + // called, and abort the program. + let _guard = AbortOnPanic; + for col in self.columns.values_mut() { + col.alloc(new_capacity); + } + core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard. + } + + /// Reallocate memory for the columns in the [`Table`] + /// + /// # Safety + /// - `current_column_capacity` is indeed the capacity of the columns + unsafe fn realloc_columns( + &mut self, + current_column_capacity: NonZeroUsize, + new_capacity: NonZeroUsize, + ) { + // If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB. + // To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be + // called, and abort the program. + let _guard = AbortOnPanic; + + // SAFETY: + // - There's no overflow + // - `current_capacity` is indeed the capacity - safety requirement + // - current capacity > 0 + for col in self.columns.values_mut() { + col.realloc(current_column_capacity, new_capacity); + } + core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard. + } + + /// Allocates space for a new entity + /// + /// # Safety + /// the allocated row must be written to immediately with valid values in each column + pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow { + self.reserve(1); + let len = self.entity_count(); + self.entities.push(entity); + for col in self.columns.values_mut() { + col.added_ticks + .initialize_unchecked(len, UnsafeCell::new(Tick::new(0))); + col.changed_ticks + .initialize_unchecked(len, UnsafeCell::new(Tick::new(0))); + #[cfg(feature = "track_change_detection")] + col.changed_by + .initialize_unchecked(len, UnsafeCell::new(Location::caller())); + } + TableRow::from_usize(len) + } + + /// Gets the number of entities currently being stored in the table. + #[inline] + pub fn entity_count(&self) -> usize { + self.entities.len() + } + + /// Get the drop function for some component that is stored in this table. + #[inline] + pub fn get_drop_for(&self, component_id: ComponentId) -> Option)> { + self.get_column(component_id)?.data.drop + } + + /// Gets the number of components being stored in the table. + #[inline] + pub fn component_count(&self) -> usize { + self.columns.len() + } + + /// Gets the maximum number of entities the table can currently store + /// without reallocating the underlying memory. + #[inline] + pub fn entity_capacity(&self) -> usize { + self.entities.capacity() + } + + /// Checks if the [`Table`] is empty or not. + /// + /// Returns `true` if the table contains no entities, `false` otherwise. + #[inline] + pub fn is_empty(&self) -> bool { + self.entities.is_empty() + } + + /// Call [`Tick::check_tick`] on all of the ticks in the [`Table`] + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + let len = self.entity_count(); + for col in self.columns.values_mut() { + // SAFETY: `len` is the actual length of the column + unsafe { col.check_change_ticks(len, change_tick) }; + } + } + + /// Iterates over the [`ThinColumn`]s of the [`Table`]. + pub fn iter_columns(&self) -> impl Iterator { + self.columns.values() + } + + /// Clears all of the stored components in the [`Table`]. + pub(crate) fn clear(&mut self) { + let len = self.entity_count(); + // We must clear the entities first, because in the drop function causes a panic, it will result in a double free of the columns. + self.entities.clear(); + for column in self.columns.values_mut() { + // SAFETY: we defer `self.entities.clear()` until after clearing the columns, + // so `self.len()` should match the columns' len + unsafe { column.clear(len) }; + } + } + + /// Moves component data out of the [`Table`]. + /// + /// This function leaves the underlying memory unchanged, but the component behind + /// returned pointer is semantically owned by the caller and will not be dropped in its original location. + /// Caller is responsible to drop component data behind returned pointer. + /// + /// # Safety + /// - This table must hold the component matching `component_id` + /// - `row` must be in bounds + /// - The row's inconsistent state that happens after taking the component must be resolved—either initialize a new component or remove the row. + pub(crate) unsafe fn take_component( + &mut self, + component_id: ComponentId, + row: TableRow, + ) -> OwningPtr<'_> { + self.get_column_mut(component_id) + .debug_checked_unwrap() + .data + .get_unchecked_mut(row.as_usize()) + .promote() + } + + /// Get the component at a given `row`, if the [`Table`] stores components with the given `component_id` + /// + /// # Safety + /// `row.as_usize()` < `self.len()` + pub unsafe fn get_component( + &self, + component_id: ComponentId, + row: TableRow, + ) -> Option> { + self.get_column(component_id) + .map(|col| col.data.get_unchecked(row.as_usize())) + } +} + +/// A collection of [`Table`] storages, indexed by [`TableId`] +/// +/// Can be accessed via [`Storages`](crate::storage::Storages) +pub struct Tables { + tables: Vec
, + table_ids: HashMap, TableId>, +} + +impl Default for Tables { + fn default() -> Self { + let empty_table = TableBuilder::with_capacity(0, 0).build(); + Tables { + tables: vec![empty_table], + table_ids: HashMap::default(), + } + } +} + +pub(crate) struct TableMoveResult { + pub swapped_entity: Option, + pub new_row: TableRow, +} + +impl Tables { + /// Returns the number of [`Table`]s this collection contains + #[inline] + pub fn len(&self) -> usize { + self.tables.len() + } + + /// Returns true if this collection contains no [`Table`]s + #[inline] + pub fn is_empty(&self) -> bool { + self.tables.is_empty() + } + + /// Fetches a [`Table`] by its [`TableId`]. + /// + /// Returns `None` if `id` is invalid. + #[inline] + pub fn get(&self, id: TableId) -> Option<&Table> { + self.tables.get(id.as_usize()) + } + + /// Fetches mutable references to two different [`Table`]s. + /// + /// # Panics + /// + /// Panics if `a` and `b` are equal. + #[inline] + pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) { + if a.as_usize() > b.as_usize() { + let (b_slice, a_slice) = self.tables.split_at_mut(a.as_usize()); + (&mut a_slice[0], &mut b_slice[b.as_usize()]) + } else { + let (a_slice, b_slice) = self.tables.split_at_mut(b.as_usize()); + (&mut a_slice[a.as_usize()], &mut b_slice[0]) + } + } + + /// Attempts to fetch a table based on the provided components, + /// creating and returning a new [`Table`] if one did not already exist. + /// + /// # Safety + /// `component_ids` must contain components that exist in `components` + pub(crate) unsafe fn get_id_or_insert( + &mut self, + component_ids: &[ComponentId], + components: &Components, + ) -> TableId { + let tables = &mut self.tables; + let (_key, value) = self + .table_ids + .raw_entry_mut() + .from_key(component_ids) + .or_insert_with(|| { + let mut table = TableBuilder::with_capacity(0, component_ids.len()); + for component_id in component_ids { + table = table.add_column(components.get_info_unchecked(*component_id)); + } + tables.push(table.build()); + (component_ids.into(), TableId::from_usize(tables.len() - 1)) + }); + + *value + } + + /// Iterates through all of the tables stored within in [`TableId`] order. + pub fn iter(&self) -> std::slice::Iter<'_, Table> { + self.tables.iter() + } + + /// Clears all data from all [`Table`]s stored within. + pub(crate) fn clear(&mut self) { + for table in &mut self.tables { + table.clear(); + } + } + + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + for table in &mut self.tables { + table.check_change_ticks(change_tick); + } + } +} + +impl Index for Tables { + type Output = Table; + + #[inline] + fn index(&self, index: TableId) -> &Self::Output { + &self.tables[index.as_usize()] + } +} + +impl IndexMut for Tables { + #[inline] + fn index_mut(&mut self, index: TableId) -> &mut Self::Output { + &mut self.tables[index.as_usize()] + } +} + +impl Drop for Table { + fn drop(&mut self) { + let len = self.entity_count(); + let cap = self.capacity(); + self.entities.clear(); + for col in self.columns.values_mut() { + // SAFETY: `cap` and `len` are correct + unsafe { + col.drop(cap, len); + } + } + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::component::Component; + use crate::ptr::OwningPtr; + use crate::storage::Storages; + use crate::{ + component::{Components, Tick}, + entity::Entity, + storage::{TableBuilder, TableRow}, + }; + #[cfg(feature = "track_change_detection")] + use std::panic::Location; + + #[derive(Component)] + struct W(T); + + #[test] + fn table() { + let mut components = Components::default(); + let mut storages = Storages::default(); + let component_id = components.init_component::>(&mut storages); + let columns = &[component_id]; + let mut table = TableBuilder::with_capacity(0, columns.len()) + .add_column(components.get_info(component_id).unwrap()) + .build(); + let entities = (0..200).map(Entity::from_raw).collect::>(); + for entity in &entities { + // SAFETY: we allocate and immediately set data afterwards + unsafe { + let row = table.allocate(*entity); + let value: W = W(row); + OwningPtr::make(value, |value_ptr| { + table.get_column_mut(component_id).unwrap().initialize( + row, + value_ptr, + Tick::new(0), + #[cfg(feature = "track_change_detection")] + Location::caller(), + ); + }); + }; + } + + assert_eq!(table.entity_capacity(), 256); + assert_eq!(table.entity_count(), 200); + } +} diff --git a/crates/bevy_ecs/src/storage/thin_array_ptr.rs b/crates/bevy_ecs/src/storage/thin_array_ptr.rs new file mode 100644 index 0000000000..d5c36e1f04 --- /dev/null +++ b/crates/bevy_ecs/src/storage/thin_array_ptr.rs @@ -0,0 +1,314 @@ +use crate::query::DebugCheckedUnwrap; +use std::alloc::{alloc, handle_alloc_error, realloc, Layout}; +use std::mem::{needs_drop, size_of}; +use std::num::NonZeroUsize; +use std::ptr::{self, NonNull}; + +/// Similar to [`Vec`], but with the capacity and length cut out for performance reasons. +/// +/// This type can be treated as a `ManuallyDrop>` without a built in length. To avoid +/// memory leaks, [`drop`](Self::drop) must be called when no longer in use. +pub struct ThinArrayPtr { + data: NonNull, + #[cfg(debug_assertions)] + capacity: usize, +} + +impl ThinArrayPtr { + fn empty() -> Self { + #[cfg(debug_assertions)] + { + Self { + data: NonNull::dangling(), + capacity: 0, + } + } + #[cfg(not(debug_assertions))] + { + Self { + data: NonNull::dangling(), + } + } + } + + #[inline(always)] + fn set_capacity(&mut self, _capacity: usize) { + #[cfg(debug_assertions)] + { + self.capacity = _capacity; + } + } + + /// Create a new [`ThinArrayPtr`] with a given capacity. If the `capacity` is 0, this will no allocate any memory. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + let mut arr = Self::empty(); + if capacity > 0 { + // SAFETY: + // - The `current_capacity` is 0 because it was just created + unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) }; + } + arr + } + + /// Allocate memory for the array, this should only be used if not previous allocation has been made (capacity = 0) + /// The caller should update their saved `capacity` value to reflect the fact that it was changed + /// + /// # Panics + /// - Panics if the new capacity overflows `usize` + pub fn alloc(&mut self, capacity: NonZeroUsize) { + self.set_capacity(capacity.get()); + if size_of::() != 0 { + let new_layout = Layout::array::(capacity.get()) + .expect("layout should be valid (arithmetic overflow)"); + // SAFETY: + // - layout has non-zero size, `capacity` > 0, `size` > 0 (`size_of::() != 0`) + self.data = NonNull::new(unsafe { alloc(new_layout) }) + .unwrap_or_else(|| handle_alloc_error(new_layout)) + .cast(); + } + } + + /// Reallocate memory for the array, this should only be used if a previous allocation for this array has been made (capacity > 0). + /// + /// # Panics + /// - Panics if the new capacity overflows `usize` + /// + /// # Safety + /// - The current capacity is indeed greater than 0 + /// - 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()); + self.set_capacity(new_capacity.get()); + if size_of::() != 0 { + let new_layout = + Layout::array::(new_capacity.get()).expect("overflow while allocating memory"); + // SAFETY: + // - ptr was be allocated via this allocator + // - the layout of the array is the same as `Layout::array::(current_capacity)` + // - the size of `T` is non 0, and `new_capacity` > 0 + // - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)", + // since the item size is always a multiple of its align, the rounding cannot happen + // here and the overflow is handled in `Layout::array` + self.data = NonNull::new(unsafe { + realloc( + self.data.cast().as_ptr(), + // We can use `unwrap_unchecked` because this is the Layout of the current allocation, it must be valid + Layout::array::(current_capacity.get()).debug_checked_unwrap(), + new_layout.size(), + ) + }) + .unwrap_or_else(|| handle_alloc_error(new_layout)) + .cast(); + } + } + + /// Initializes the value at `index` to `value`. This function does not do any bounds checking. + /// + /// # Safety + /// `index` must be in bounds i.e. within the `capacity`. + /// if `index` = `len` the caller should update their saved `len` value to reflect the fact that it was changed + #[inline] + pub unsafe fn initialize_unchecked(&mut self, index: usize, value: T) { + // SAFETY: `index` is in bounds + let ptr = unsafe { self.get_unchecked_raw(index) }; + // SAFETY: `index` is in bounds, therefore the pointer to that location in the array is valid, and aligned. + unsafe { ptr::write(ptr, value) }; + } + + /// Get a raw pointer to the element at `index`. This method doesn't do any bounds checking. + /// + /// # Safety + /// - `index` must be safe to access. + #[inline] + pub unsafe fn get_unchecked_raw(&mut self, index: usize) -> *mut T { + // SAFETY: + // - `self.data` and the resulting pointer are in the same allocated object + // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either + unsafe { self.data.as_ptr().add(index) } + } + + /// Get a reference to the element at `index`. This method doesn't do any bounds checking. + /// + /// # Safety + /// - `index` must be safe to read. + #[inline] + pub unsafe fn get_unchecked(&self, index: usize) -> &'_ T { + // SAFETY: + // - `self.data` and the resulting pointer are in the same allocated object + // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either + let ptr = unsafe { self.data.as_ptr().add(index) }; + // SAFETY: + // - The pointer is properly aligned + // - It is derefrancable (all in the same allocation) + // - `index` < `len` and the element is safe to write to, so its valid + // - We have a reference to self, so no other mutable accesses to the element can occur + unsafe { + ptr.as_ref() + // SAFETY: We can use `unwarp_unchecked` because the pointer isn't null) + .debug_checked_unwrap() + } + } + + /// Get a mutable reference to the element at `index`. This method doesn't do any bounds checking. + /// + /// # Safety + /// - `index` must be safe to write to. + #[inline] + pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &'_ mut T { + // SAFETY: + // - `self.data` and the resulting pointer are in the same allocated object + // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either + let ptr = unsafe { self.data.as_ptr().add(index) }; + // SAFETY: + // - The pointer is properly aligned + // - It is derefrancable (all in the same allocation) + // - `index` < `len` and the element is safe to write to, so its valid + // - We have a mutable reference to `self` + unsafe { + ptr.as_mut() + // SAFETY: We can use `unwarp_unchecked` because the pointer isn't null) + .unwrap_unchecked() + } + } + + /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_unchecked_nonoverlapping( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) -> T { + #[cfg(debug_assertions)] + { + debug_assert!(self.capacity > index_to_keep); + debug_assert!(self.capacity > index_to_remove); + debug_assert_ne!(index_to_keep, index_to_remove); + } + let base_ptr = self.data.as_ptr(); + let value = ptr::read(base_ptr.add(index_to_remove)); + ptr::copy_nonoverlapping( + base_ptr.add(index_to_keep), + base_ptr.add(index_to_remove), + 1, + ); + value + } + + /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_unchecked( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) -> T { + if index_to_remove != index_to_keep { + return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep); + } + ptr::read(self.data.as_ptr().add(index_to_remove)) + } + + /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and drop the removed value. + /// + /// # Safety + /// - `index_to_keep` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` must be safe to access (within the bounds of the length of the array). + /// - `index_to_remove` != `index_to_keep` + /// - The caller should address the inconsistent state of the array that has occurred after the swap, either: + /// 1) initialize a different value in `index_to_keep` + /// 2) update the saved length of the array if `index_to_keep` was the last element. + #[inline] + pub unsafe fn swap_remove_and_drop_unchecked( + &mut self, + index_to_remove: usize, + index_to_keep: usize, + ) { + let val = &mut self.swap_remove_unchecked(index_to_remove, index_to_keep); + ptr::drop_in_place(ptr::from_mut(val)); + } + + /// Get a raw pointer to the last element of the array, return `None` if the length is 0 + /// + /// # Safety + /// - ensure that `current_len` is indeed the len of the array + #[inline] + unsafe fn last_element(&mut self, current_len: usize) -> Option<*mut T> { + (current_len != 0).then_some(self.data.as_ptr().add(current_len - 1)) + } + + /// Clears the array, removing (and dropping) Note that this method has no effect on the allocated capacity of the vector. + /// + /// # Safety + /// - `current_len` is indeed the length of the array + /// - The caller should update their saved length value + pub unsafe fn clear_elements(&mut self, mut current_len: usize) { + if needs_drop::() { + while let Some(to_drop) = self.last_element(current_len) { + ptr::drop_in_place(to_drop); + current_len -= 1; + } + } + } + + /// Drop the entire array and all its elements. + /// + /// # Safety + /// - `current_len` is indeed the length of the array + /// - `current_capacity` is indeed the capacity of the array + /// - The caller must not use this `ThinArrayPtr` in any way after calling this function + pub unsafe fn drop(&mut self, current_capacity: usize, current_len: usize) { + #[cfg(debug_assertions)] + assert_eq!(self.capacity, current_capacity); + if current_capacity != 0 { + self.clear_elements(current_len); + let layout = Layout::array::(current_capacity).expect("layout should be valid"); + std::alloc::dealloc(self.data.as_ptr().cast(), layout); + } + self.set_capacity(0); + } + + /// Get the [`ThinArrayPtr`] as a slice with a given length. + /// + /// # Safety + /// - `slice_len` must match the actual length of the array + #[inline] + pub unsafe fn as_slice(&self, slice_len: usize) -> &[T] { + // SAFETY: + // - the data is valid - allocated with the same allocater + // - non-null and well-aligned + // - we have a shared reference to self - the data will not be mutated during 'a + unsafe { std::slice::from_raw_parts(self.data.as_ptr(), slice_len) } + } +} + +impl From> for ThinArrayPtr { + fn from(value: Box<[T]>) -> Self { + let _len = value.len(); + let slice_ptr = Box::<[T]>::into_raw(value); + // SAFETY: We just got the pointer from a reference + let first_element_ptr = unsafe { (*slice_ptr).as_mut_ptr() }; + Self { + // SAFETY: The pointer can't be null, it came from a reference + data: unsafe { NonNull::new_unchecked(first_element_ptr) }, + #[cfg(debug_assertions)] + capacity: _len, + } + } +} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index c6676fa26a..9933243cf8 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,12 +1,14 @@ mod parallel_scope; use core::panic::Location; +use std::marker::PhantomData; use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource}; use crate::{ self as bevy_ecs, bundle::{Bundle, InsertMode}, - component::{ComponentId, ComponentInfo}, + change_detection::Mut, + component::{Component, ComponentId, ComponentInfo}, entity::{Entities, Entity}, event::{Event, SendEvent}, observer::{Observer, TriggerEvent, TriggerTargets}, @@ -54,7 +56,7 @@ pub use parallel_scope::*; /// /// Each built-in command is implemented as a separate method, e.g. [`Commands::spawn`]. /// In addition to the pre-defined command methods, you can add commands with any arbitrary -/// behavior using [`Commands::add`], which accepts any type implementing [`Command`]. +/// behavior using [`Commands::queue`], which accepts any type implementing [`Command`]. /// /// Since closures and other functions implement this trait automatically, this allows one-shot, /// anonymous custom commands. @@ -63,7 +65,7 @@ pub use parallel_scope::*; /// # use bevy_ecs::prelude::*; /// # fn foo(mut commands: Commands) { /// // NOTE: type inference fails here, so annotations are required on the closure. -/// commands.add(|w: &mut World| { +/// commands.queue(|w: &mut World| { /// // Mutate the world however you want... /// # todo!(); /// }); @@ -303,7 +305,7 @@ impl<'w, 's> Commands<'w, 's> { /// apps, and only when they have a scheme worked out to share an ID space (which doesn't happen /// by default). pub fn get_or_spawn(&mut self, entity: Entity) -> EntityCommands { - self.add(move |world: &mut World| { + self.queue(move |world: &mut World| { world.get_or_spawn(entity); }); EntityCommands { @@ -503,11 +505,43 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, I::Item: Bundle, { - self.push(spawn_batch(bundles_iter)); + self.queue(spawn_batch(bundles_iter)); } - /// Push a [`Command`] onto the queue. - pub fn push(&mut self, command: C) { + /// Pushes a generic [`Command`] to the command queue. + /// + /// `command` can be a built-in command, custom struct that implements [`Command`] or a closure + /// that takes [`&mut World`](World) as an argument. + /// # Example + /// + /// ``` + /// # use bevy_ecs::{world::Command, prelude::*}; + /// #[derive(Resource, Default)] + /// struct Counter(u64); + /// + /// struct AddToCounter(u64); + /// + /// impl Command for AddToCounter { + /// fn apply(self, world: &mut World) { + /// let mut counter = world.get_resource_or_insert_with(Counter::default); + /// counter.0 += self.0; + /// } + /// } + /// + /// fn add_three_to_counter_system(mut commands: Commands) { + /// commands.queue(AddToCounter(3)); + /// } + /// fn add_twenty_five_to_counter_system(mut commands: Commands) { + /// commands.queue(|world: &mut World| { + /// let mut counter = world.get_resource_or_insert_with(Counter::default); + /// counter.0 += 25; + /// }); + /// } + + /// # bevy_ecs::system::assert_is_system(add_three_to_counter_system); + /// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system); + /// ``` + pub fn queue(&mut self, command: C) { match &mut self.queue { InternalQueue::CommandQueue(queue) => { queue.push(command); @@ -549,7 +583,7 @@ impl<'w, 's> Commands<'w, 's> { I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.push(insert_or_spawn_batch(bundles_iter)); + self.queue(insert_or_spawn_batch(bundles_iter)); } /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value. @@ -578,7 +612,7 @@ impl<'w, 's> Commands<'w, 's> { /// ``` #[track_caller] pub fn init_resource(&mut self) { - self.push(init_resource::); + self.queue(init_resource::); } /// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value. @@ -608,7 +642,7 @@ impl<'w, 's> Commands<'w, 's> { /// ``` #[track_caller] pub fn insert_resource(&mut self, resource: R) { - self.push(insert_resource(resource)); + self.queue(insert_resource(resource)); } /// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`]. @@ -632,7 +666,7 @@ impl<'w, 's> Commands<'w, 's> { /// # bevy_ecs::system::assert_is_system(system); /// ``` pub fn remove_resource(&mut self) { - self.push(remove_resource::); + self.queue(remove_resource::); } /// Runs the system corresponding to the given [`SystemId`]. @@ -658,7 +692,7 @@ impl<'w, 's> Commands<'w, 's> { /// execution of the system happens later. To get the output of a system, use /// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. pub fn run_system_with_input(&mut self, id: SystemId, input: I) { - self.push(RunSystemWithInput::new_with_input(id, input)); + self.queue(RunSystemWithInput::new_with_input(id, input)); } /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. @@ -720,53 +754,16 @@ impl<'w, 's> Commands<'w, 's> { system: S, ) -> SystemId { let entity = self.spawn_empty().id(); - self.push(RegisterSystem::new(system, entity)); + self.queue(RegisterSystem::new(system, entity)); SystemId::from_entity(entity) } - /// Pushes a generic [`Command`] to the command queue. - /// - /// `command` can be a built-in command, custom struct that implements [`Command`] or a closure - /// that takes [`&mut World`](World) as an argument. - /// # Example - /// - /// ``` - /// # use bevy_ecs::{world::Command, prelude::*}; - /// #[derive(Resource, Default)] - /// struct Counter(u64); - /// - /// struct AddToCounter(u64); - /// - /// impl Command for AddToCounter { - /// fn apply(self, world: &mut World) { - /// let mut counter = world.get_resource_or_insert_with(Counter::default); - /// counter.0 += self.0; - /// } - /// } - /// - /// fn add_three_to_counter_system(mut commands: Commands) { - /// commands.add(AddToCounter(3)); - /// } - /// fn add_twenty_five_to_counter_system(mut commands: Commands) { - /// commands.add(|world: &mut World| { - /// let mut counter = world.get_resource_or_insert_with(Counter::default); - /// counter.0 += 25; - /// }); - /// } - - /// # bevy_ecs::system::assert_is_system(add_three_to_counter_system); - /// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system); - /// ``` - pub fn add(&mut self, command: C) { - self.push(command); - } - /// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that /// isn't scoped to specific targets. /// /// [`Trigger`]: crate::observer::Trigger pub fn trigger(&mut self, event: impl Event) { - self.add(TriggerEvent { event, targets: () }); + self.queue(TriggerEvent { event, targets: () }); } /// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that @@ -778,7 +775,7 @@ impl<'w, 's> Commands<'w, 's> { event: impl Event, targets: impl TriggerTargets + Send + Sync + 'static, ) { - self.add(TriggerEvent { event, targets }); + self.queue(TriggerEvent { event, targets }); } /// Spawns an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer. @@ -800,7 +797,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// [`EventWriter`]: crate::event::EventWriter pub fn send_event(&mut self, event: E) -> &mut Self { - self.add(SendEvent { event }); + self.queue(SendEvent { event }); self } } @@ -848,8 +845,8 @@ impl<'w, 's> Commands<'w, 's> { /// # assert_schedule.run(&mut world); /// /// fn setup(mut commands: Commands) { -/// commands.spawn_empty().add(count_name); -/// commands.spawn_empty().add(count_name); +/// commands.spawn_empty().queue(count_name); +/// commands.spawn_empty().queue(count_name); /// } /// /// fn assert_names(named: Query<&Name>) { @@ -911,6 +908,38 @@ impl EntityCommands<'_> { } } + /// Get an [`EntityEntryCommands`] for the [`Component`] `T`, + /// allowing you to modify it or insert it if it isn't already present. + /// + /// See also [`insert_if_new`](Self::insert_if_new), which lets you insert a [`Bundle`] without overwriting it. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct PlayerEntity { entity: Entity } + /// #[derive(Component)] + /// struct Level(u32); + /// + /// fn level_up_system(mut commands: Commands, player: Res) { + /// commands + /// .entity(player.entity) + /// .entry::() + /// // Modify the component if it exists + /// .and_modify(|mut lvl| lvl.0 += 1) + /// // Otherwise insert a default value + /// .or_insert(Level(0)); + /// } + /// # bevy_ecs::system::assert_is_system(level_up_system); + /// ``` + pub fn entry(&mut self) -> EntityEntryCommands { + EntityEntryCommands { + entity_commands: self.reborrow(), + marker: PhantomData, + } + } + /// Adds a [`Bundle`] of components to the entity. /// /// This will overwrite any previous value(s) of the same component type. @@ -965,7 +994,7 @@ impl EntityCommands<'_> { /// ``` #[track_caller] pub fn insert(self, bundle: impl Bundle) -> Self { - self.add(insert(bundle, InsertMode::Replace)) + self.queue(insert(bundle, InsertMode::Replace)) } /// Similar to [`Self::insert`] but will only insert if the predicate returns true. @@ -1003,7 +1032,7 @@ impl EntityCommands<'_> { F: FnOnce() -> bool, { if condition() { - self.add(insert(bundle, InsertMode::Replace)) + self.queue(insert(bundle, InsertMode::Replace)) } else { self } @@ -1015,6 +1044,25 @@ impl EntityCommands<'_> { /// components will leave the old values instead of replacing them with new /// ones. /// + /// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present, + /// as well as initialize it with a default value. + /// + /// # Panics + /// + /// The command will panic when applied if the associated entity does not exist. + /// + /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] instead. + pub fn insert_if_new(self, bundle: impl Bundle) -> Self { + self.queue(insert(bundle, InsertMode::Keep)) + } + + /// Adds a [`Bundle`] of components to the entity without overwriting if the + /// predicate returns true. + /// + /// This is the same as [`EntityCommands::insert_if`], but in case of duplicate + /// components will leave the old values instead of replacing them with new + /// ones. + /// /// # Panics /// /// The command will panic when applied if the associated entity does not @@ -1022,8 +1070,15 @@ impl EntityCommands<'_> { /// /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] /// instead. - pub fn insert_if_new(self, bundle: impl Bundle) -> Self { - self.add(insert(bundle, InsertMode::Keep)) + pub fn insert_if_new_and(self, bundle: impl Bundle, condition: F) -> Self + where + F: FnOnce() -> bool, + { + if condition() { + self.insert_if_new(bundle) + } else { + self + } } /// Adds a dynamic component to an entity. @@ -1048,7 +1103,7 @@ impl EntityCommands<'_> { ) -> Self { let caller = Location::caller(); // SAFETY: same invariants as parent call - self.add(unsafe {insert_by_id(component_id, value, move |entity| { + self.queue(unsafe {insert_by_id(component_id, value, move |entity| { panic!("error[B0003]: {caller}: Could not insert a component {component_id:?} (with type {}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::()); })}) } @@ -1067,7 +1122,7 @@ impl EntityCommands<'_> { value: T, ) -> Self { // SAFETY: same invariants as parent call - self.add(unsafe { insert_by_id(component_id, value, |_| {}) }) + self.queue(unsafe { insert_by_id(component_id, value, |_| {}) }) } /// Tries to add a [`Bundle`] of components to the entity. @@ -1120,7 +1175,7 @@ impl EntityCommands<'_> { /// ``` #[track_caller] pub fn try_insert(self, bundle: impl Bundle) -> Self { - self.add(try_insert(bundle, InsertMode::Replace)) + self.queue(try_insert(bundle, InsertMode::Replace)) } /// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true. @@ -1155,7 +1210,53 @@ impl EntityCommands<'_> { F: FnOnce() -> bool, { if condition() { - self.add(try_insert(bundle, InsertMode::Replace)) + self.queue(try_insert(bundle, InsertMode::Replace)) + } else { + self + } + } + + /// Tries to add a [`Bundle`] of components to the entity without overwriting if the + /// predicate returns true. + /// + /// This is the same as [`EntityCommands::try_insert_if`], but in case of duplicate + /// components will leave the old values instead of replacing them with new + /// ones. + /// + /// # Note + /// + /// Unlike [`Self::insert_if_new_and`], this will not panic if the associated entity does + /// not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource)] + /// # struct PlayerEntity { entity: Entity } + /// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } } + /// #[derive(Component)] + /// struct StillLoadingStats; + /// #[derive(Component)] + /// struct Health(u32); + /// + /// fn add_health_system(mut commands: Commands, player: Res) { + /// commands.entity(player.entity) + /// .try_insert_if(Health(10), || player.is_spectator()) + /// .remove::(); + /// + /// commands.entity(player.entity) + /// // This will not panic nor will it overwrite the component + /// .try_insert_if_new_and(Health(5), || player.is_spectator()); + /// } + /// # bevy_ecs::system::assert_is_system(add_health_system); + /// ``` + pub fn try_insert_if_new_and(self, bundle: impl Bundle, condition: F) -> Self + where + F: FnOnce() -> bool, + { + if condition() { + self.try_insert_if_new(bundle) } else { self } @@ -1171,7 +1272,7 @@ impl EntityCommands<'_> { /// /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. pub fn try_insert_if_new(self, bundle: impl Bundle) -> Self { - self.add(try_insert(bundle, InsertMode::Keep)) + self.queue(try_insert(bundle, InsertMode::Keep)) } /// Removes a [`Bundle`] of components from the entity. @@ -1213,17 +1314,17 @@ impl EntityCommands<'_> { where T: Bundle, { - self.add(remove::) + self.queue(remove::) } /// Removes a component from the entity. pub fn remove_by_id(self, component_id: ComponentId) -> Self { - self.add(remove_by_id(component_id)) + self.queue(remove_by_id(component_id)) } /// Removes all components associated with the entity. pub fn clear(self) -> Self { - self.add(clear()) + self.queue(clear()) } /// Despawns the entity. @@ -1255,7 +1356,7 @@ impl EntityCommands<'_> { /// ``` #[track_caller] pub fn despawn(self) -> Self { - self.add(despawn()) + self.queue(despawn()) } /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. @@ -1268,15 +1369,15 @@ impl EntityCommands<'_> { /// commands /// .spawn_empty() /// // Closures with this signature implement `EntityCommand`. - /// .add(|entity: EntityWorldMut| { + /// .queue(|entity: EntityWorldMut| { /// println!("Executed an EntityCommand for {:?}", entity.id()); /// }); /// # } /// # bevy_ecs::system::assert_is_system(my_system); /// ``` #[allow(clippy::should_implement_trait)] - pub fn add(mut self, command: impl EntityCommand) -> Self { - self.commands.add(command.with_entity(self.entity)); + pub fn queue(mut self, command: impl EntityCommand) -> Self { + self.commands.queue(command.with_entity(self.entity)); self } @@ -1321,7 +1422,7 @@ impl EntityCommands<'_> { where T: Bundle, { - self.add(retain::) + self.queue(retain::) } /// Logs the components of the entity at the info level. @@ -1330,7 +1431,7 @@ impl EntityCommands<'_> { /// /// The command will panic when applied if the associated entity does not exist. pub fn log_components(self) -> Self { - self.add(log_components) + self.queue(log_components) } /// Returns the underlying [`Commands`]. @@ -1349,7 +1450,114 @@ impl EntityCommands<'_> { /// Creates an [`Observer`] listening for a trigger of type `T` that targets this entity. pub fn observe(self, system: impl IntoObserverSystem) -> Self { - self.add(observe(system)) + self.queue(observe(system)) + } +} + +/// A wrapper around [`EntityCommands`] with convenience methods for working with a specified component type. +pub struct EntityEntryCommands<'a, T> { + entity_commands: EntityCommands<'a>, + marker: PhantomData, +} + +impl<'a, T: Component> EntityEntryCommands<'a, T> { + /// Modify the component `T` if it exists, using the the function `modify`. + pub fn and_modify(mut self, modify: impl FnOnce(Mut) + Send + Sync + 'static) -> Self { + self.entity_commands = self + .entity_commands + .queue(move |mut entity: EntityWorldMut| { + if let Some(value) = entity.get_mut() { + modify(value); + } + }); + self + } + + /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. + /// + /// See also [`or_insert_with`](Self::or_insert_with). + /// + /// # Panics + /// + /// Panics if the entity does not exist. + /// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version. + #[track_caller] + pub fn or_insert(mut self, default: T) -> Self { + self.entity_commands = self + .entity_commands + .queue(insert(default, InsertMode::Keep)); + self + } + + /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. + /// + /// Unlike [`or_insert`](Self::or_insert), this will not panic if the entity does not exist. + /// + /// See also [`or_insert_with`](Self::or_insert_with). + #[track_caller] + pub fn or_try_insert(mut self, default: T) -> Self { + self.entity_commands = self + .entity_commands + .queue(try_insert(default, InsertMode::Keep)); + self + } + + /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. + /// + /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). + /// + /// # Panics + /// + /// Panics if the entity does not exist. + /// See [`or_try_insert_with`](Self::or_try_insert_with) for a non-panicking version. + #[track_caller] + pub fn or_insert_with(self, default: impl Fn() -> T) -> Self { + self.or_insert(default()) + } + + /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. + /// + /// Unlike [`or_insert_with`](Self::or_insert_with), this will not panic if the entity does not exist. + /// + /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). + #[track_caller] + pub fn or_try_insert_with(self, default: impl Fn() -> T) -> Self { + self.or_try_insert(default()) + } + + /// [Insert](EntityCommands::insert) `T::default` into this entity, if `T` is not already present. + /// + /// See also [`or_insert`](Self::or_insert) and [`or_from_world`](Self::or_from_world). + /// + /// # Panics + /// + /// Panics if the entity does not exist. + #[track_caller] + pub fn or_default(self) -> Self + where + T: Default, + { + #[allow(clippy::unwrap_or_default)] + // FIXME: use `expect` once stable + self.or_insert(T::default()) + } + + /// [Insert](EntityCommands::insert) `T::from_world` into this entity, if `T` is not already present. + /// + /// See also [`or_insert`](Self::or_insert) and [`or_default`](Self::or_default). + /// + /// # Panics + /// + /// Panics if the entity does not exist. + #[track_caller] + pub fn or_from_world(mut self) -> Self + where + T: FromWorld, + { + self.entity_commands = self + .entity_commands + .queue(insert_from_world::(InsertMode::Keep)); + self } } @@ -1461,6 +1669,25 @@ fn insert(bundle: T, mode: InsertMode) -> impl EntityCommand { } } +/// An [`EntityCommand`] that adds the component using its `FromWorld` implementation. +#[track_caller] +fn insert_from_world(mode: InsertMode) -> impl EntityCommand { + let caller = Location::caller(); + move |entity: Entity, world: &mut World| { + let value = T::from_world(world); + if let Some(mut entity) = world.get_entity_mut(entity) { + entity.insert_with_caller( + value, + mode, + #[cfg(feature = "track_change_detection")] + caller, + ); + } else { + panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::(), entity); + } + } +} + /// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity. /// Does nothing if the entity does not exist. #[track_caller] @@ -1596,7 +1823,7 @@ mod tests { self as bevy_ecs, component::Component, system::{Commands, Resource}, - world::{CommandQueue, World}, + world::{CommandQueue, FromWorld, World}, }; use std::{ any::TypeId, @@ -1633,6 +1860,50 @@ mod tests { world.spawn((W(0u32), W(42u64))); } + impl FromWorld for W { + fn from_world(world: &mut World) -> Self { + let v = world.resource::>(); + Self("*".repeat(v.0)) + } + } + + #[test] + fn entity_commands_entry() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + let entity = commands.spawn_empty().id(); + commands + .entity(entity) + .entry::>() + .and_modify(|_| unreachable!()); + queue.apply(&mut world); + assert!(!world.entity(entity).contains::>()); + let mut commands = Commands::new(&mut queue, &world); + commands + .entity(entity) + .entry::>() + .or_insert(W(0)) + .and_modify(|mut val| { + val.0 = 21; + }); + queue.apply(&mut world); + assert_eq!(21, world.get::>(entity).unwrap().0); + let mut commands = Commands::new(&mut queue, &world); + commands + .entity(entity) + .entry::>() + .and_modify(|_| unreachable!()) + .or_insert(W(42)); + queue.apply(&mut world); + assert_eq!(42, world.get::>(entity).unwrap().0); + world.insert_resource(W(5_usize)); + let mut commands = Commands::new(&mut queue, &world); + commands.entity(entity).entry::>().or_from_world(); + queue.apply(&mut world); + assert_eq!("*****", &world.get::>(entity).unwrap().0); + } + #[test] fn commands() { let mut world = World::default(); @@ -1667,12 +1938,12 @@ mod tests { let mut commands = Commands::new(&mut command_queue, &world); // set up a simple command using a closure that adds one additional entity - commands.add(|world: &mut World| { + commands.queue(|world: &mut World| { world.spawn((W(42u32), W(0u64))); }); // set up a simple command using a function that adds one additional entity - commands.add(simple_command); + commands.queue(simple_command); } command_queue.apply(&mut world); let results3 = world @@ -1684,6 +1955,45 @@ mod tests { assert_eq!(results3, vec![(42u32, 0u64), (0u32, 42u64)]); } + #[test] + fn insert_components() { + let mut world = World::default(); + let mut command_queue1 = CommandQueue::default(); + + // insert components + let entity = Commands::new(&mut command_queue1, &world) + .spawn(()) + .insert_if(W(1u8), || true) + .insert_if(W(2u8), || false) + .insert_if_new(W(1u16)) + .insert_if_new(W(2u16)) + .insert_if_new_and(W(1u32), || false) + .insert_if_new_and(W(2u32), || true) + .insert_if_new_and(W(3u32), || true) + .id(); + command_queue1.apply(&mut world); + + let results = world + .query::<(&W, &W, &W)>() + .iter(&world) + .map(|(a, b, c)| (a.0, b.0, c.0)) + .collect::>(); + assert_eq!(results, vec![(1u8, 1u16, 2u32)]); + + // try to insert components after despawning entity + // in another command queue + Commands::new(&mut command_queue1, &world) + .entity(entity) + .try_insert_if_new_and(W(1u64), || true); + + let mut command_queue2 = CommandQueue::default(); + Commands::new(&mut command_queue2, &world) + .entity(entity) + .despawn(); + command_queue2.apply(&mut world); + command_queue1.apply(&mut world); + } + #[test] fn remove_components() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 4eb8301d9b..9de3ab40a4 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1493,13 +1493,10 @@ mod tests { // set up system and verify its access is empty system.initialize(&mut world); system.update_archetype_component_access(world.as_unsafe_world_cell()); - assert_eq!( - system - .archetype_component_access() - .component_reads() - .collect::>(), - expected_ids - ); + let archetype_component_access = system.archetype_component_access(); + assert!(expected_ids + .iter() + .all(|id| archetype_component_access.has_component_read(*id))); // add some entities with archetypes that should match and save their ids expected_ids.insert( @@ -1523,13 +1520,10 @@ mod tests { // update system and verify its accesses are correct system.update_archetype_component_access(world.as_unsafe_world_cell()); - assert_eq!( - system - .archetype_component_access() - .component_reads() - .collect::>(), - expected_ids - ); + let archetype_component_access = system.archetype_component_access(); + assert!(expected_ids + .iter() + .all(|id| archetype_component_access.has_component_read(*id))); // one more round expected_ids.insert( @@ -1541,13 +1535,10 @@ mod tests { ); world.spawn((A, B, D)); system.update_archetype_component_access(world.as_unsafe_world_cell()); - assert_eq!( - system - .archetype_component_access() - .component_reads() - .collect::>(), - expected_ids - ); + let archetype_component_access = system.archetype_component_access(); + assert!(expected_ids + .iter() + .all(|id| archetype_component_access.has_component_read(*id))); } #[test] diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index ffe595b2cf..ddff01e19f 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -616,13 +616,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many( + pub fn iter_many>>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> 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. @@ -670,13 +667,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # bevy_ecs::system::assert_is_system(system); /// ``` #[inline] - pub fn iter_many_mut( + pub fn iter_many_mut>>( &mut self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { self.state.iter_many_unchecked_manual( @@ -752,13 +746,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_many_mut`](Self::iter_many_mut) to safely access the query items. - pub unsafe fn iter_many_unsafe( + pub unsafe fn iter_many_unsafe>>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> 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. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 6b604c54c2..48b9910d2b 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1894,7 +1894,9 @@ unsafe impl ReadOnlySystemParam for PhantomData {} /// assert!(param.is::>()); /// assert!(!param.is::>()); /// assert!(param.downcast_mut::>().is_none()); -/// let foo: Res = param.downcast::>().unwrap(); +/// let res = param.downcast_mut::>().unwrap(); +/// // The type parameter can be left out if it can be determined from use. +/// let res: Res = param.downcast().unwrap(); /// } /// /// let system = ( @@ -1921,7 +1923,7 @@ pub struct DynSystemParam<'w, 's> { } impl<'w, 's> DynSystemParam<'w, 's> { - /// # SAFETY + /// # Safety /// - `state` must be a `ParamState` for some inner `T: SystemParam`. /// - The passed [`UnsafeWorldCell`] must have access to any world data registered /// in [`init_state`](SystemParam::init_state) for the inner system param. @@ -1942,13 +1944,21 @@ impl<'w, 's> DynSystemParam<'w, 's> { } /// Returns `true` if the inner system param is the same as `T`. - pub fn is(&self) -> bool { - self.state.is::>() + pub fn is(&self) -> bool + // See downcast() function for an explanation of the where clause + where + T::Item<'static, 'static>: SystemParam = T> + 'static, + { + self.state.is::>>() } /// Returns the inner system param if it is the correct type. /// This consumes the dyn param, so the returned param can have its original world and state lifetimes. - pub fn downcast(self) -> Option> { + pub fn downcast(self) -> Option + // See downcast() function for an explanation of the where clause + where + T::Item<'static, 'static>: SystemParam = T> + 'static, + { // SAFETY: // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, // and that it has access required by the inner system param. @@ -1958,7 +1968,11 @@ impl<'w, 's> DynSystemParam<'w, 's> { /// Returns the inner system parameter if it is the correct type. /// This borrows the dyn param, so the returned param is only valid for the duration of that borrow. - pub fn downcast_mut(&mut self) -> Option> { + pub fn downcast_mut<'a, T: SystemParam>(&'a mut self) -> Option + // See downcast() function for an explanation of the where clause + where + T::Item<'static, 'static>: SystemParam = T> + 'static, + { // SAFETY: // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, // and that it has access required by the inner system param. @@ -1971,9 +1985,11 @@ impl<'w, 's> DynSystemParam<'w, 's> { /// but since it only performs read access it can keep the original world lifetime. /// This can be useful with methods like [`Query::iter_inner()`] or [`Res::into_inner()`] /// to obtain references with the original world lifetime. - pub fn downcast_mut_inner( - &mut self, - ) -> Option> { + pub fn downcast_mut_inner<'a, T: ReadOnlySystemParam>(&'a mut self) -> Option + // See downcast() function for an explanation of the where clause + where + T::Item<'static, 'static>: SystemParam = T> + 'static, + { // SAFETY: // - `DynSystemParam::new()` ensures `state` is a `ParamState`, that the world matches, // and that it has access required by the inner system param. @@ -1982,25 +1998,38 @@ impl<'w, 's> DynSystemParam<'w, 's> { } } -/// # SAFETY +/// # Safety /// - `state` must be a `ParamState` for some inner `T: SystemParam`. /// - The passed [`UnsafeWorldCell`] must have access to any world data registered /// in [`init_state`](SystemParam::init_state) for the inner system param. /// - `world` must be the same `World` that was used to initialize /// [`state`](SystemParam::init_state) for the inner system param. -unsafe fn downcast<'w, 's, T: SystemParam + 'static>( +unsafe fn downcast<'w, 's, T: SystemParam>( state: &'s mut dyn Any, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, -) -> Option> { - state.downcast_mut::>().map(|state| { - // SAFETY: - // - The caller ensures the world has access for the underlying system param, - // and since the downcast succeeded, the underlying system param is T. - // - The caller ensures the `world` matches. - unsafe { T::get_param(&mut state.0, system_meta, world, change_tick) } - }) +) -> Option +// We need a 'static version of the SystemParam to use with `Any::downcast_mut()`, +// and we need a <'w, 's> version to actually return. +// The type parameter T must be the one we return in order to get type inference from the return value. +// So we use `T::Item<'static, 'static>` as the 'static version, and require that it be 'static. +// That means the return value will be T::Item<'static, 'static>::Item<'w, 's>, +// so we constrain that to be equal to T. +// Every actual `SystemParam` implementation has `T::Item == T` up to lifetimes, +// so they should all work with this constraint. +where + T::Item<'static, 'static>: SystemParam = T> + 'static, +{ + state + .downcast_mut::>>() + .map(|state| { + // SAFETY: + // - The caller ensures the world has access for the underlying system param, + // and since the downcast succeeded, the underlying system param is T. + // - The caller ensures the `world` matches. + unsafe { T::Item::get_param(&mut state.0, system_meta, world, change_tick) } + }) } /// The [`SystemParam::State`] for a [`DynSystemParam`]. @@ -2323,4 +2352,12 @@ mod tests { schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set)); schedule.run(&mut world); } + + fn _dyn_system_param_type_inference(mut p: DynSystemParam) { + // Make sure the downcast() methods are able to infer their type parameters from the use of the return type. + // This is just a compilation test, so there is nothing to run. + let _query: Query<()> = p.downcast_mut().unwrap(); + let _query: Query<()> = p.downcast_mut_inner().unwrap(); + let _query: Query<()> = p.downcast().unwrap(); + } } diff --git a/crates/bevy_ecs/src/world/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs index e2a0a29f40..e78ce8b03e 100644 --- a/crates/bevy_ecs/src/world/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -479,20 +479,20 @@ mod test { fn add_index(index: usize) -> impl Command { move |world: &mut World| world.resource_mut::().0.push(index) } - world.commands().add(add_index(1)); - world.commands().add(|world: &mut World| { - world.commands().add(add_index(2)); - world.commands().add(PanicCommand("I panic!".to_owned())); - world.commands().add(add_index(3)); + world.commands().queue(add_index(1)); + world.commands().queue(|world: &mut World| { + world.commands().queue(add_index(2)); + world.commands().queue(PanicCommand("I panic!".to_owned())); + world.commands().queue(add_index(3)); world.flush_commands(); }); - world.commands().add(add_index(4)); + world.commands().queue(add_index(4)); let _ = panic::catch_unwind(AssertUnwindSafe(|| { world.flush_commands(); })); - world.commands().add(add_index(5)); + world.commands().queue(add_index(5)); world.flush_commands(); assert_eq!(&world.resource::().0, &[1, 2, 3, 4, 5]); } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 5adfb2d302..91d021b1fa 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -6,7 +6,7 @@ use crate::{ entity::{Entities, Entity, EntityLocation}, event::Event, observer::{Observer, Observers}, - query::Access, + query::{Access, ReadOnlyQueryData}, removal_detection::RemovedComponentEvents, storage::Storages, system::IntoObserverSystem, @@ -156,6 +156,22 @@ impl<'w> EntityRef<'w> { // SAFETY: We have read-only access to all components of this entity. unsafe { self.0.get_by_id(component_id) } } + + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + pub fn components(&self) -> Q::Item<'w> { + self.get_components::().expect(QUERY_MISMATCH_ERROR) + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + pub fn get_components(&self) -> Option> { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.0.get_components::() } + } } impl<'w> From> for EntityRef<'w> { @@ -351,6 +367,22 @@ impl<'w> EntityMut<'w> { self.as_readonly().get() } + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + pub fn components(&self) -> Q::Item<'_> { + self.get_components::().expect(QUERY_MISMATCH_ERROR) + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + pub fn get_components(&self) -> Option> { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.0.get_components::() } + } + /// Consumes `self` and gets access to the component of type `T` with the /// world `'w` lifetime for the current entity. /// @@ -648,6 +680,23 @@ impl<'w> EntityWorldMut<'w> { EntityRef::from(self).get() } + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + #[inline] + pub fn components(&self) -> Q::Item<'_> { + EntityRef::from(self).components::() + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + #[inline] + pub fn get_components(&self) -> Option> { + EntityRef::from(self).get_components::() + } + /// Consumes `self` and gets access to the component of type `T` with /// the world `'w` lifetime for the current entity. /// Returns `None` if the entity does not have a component of type `T`. @@ -1081,7 +1130,7 @@ impl<'w> EntityWorldMut<'w> { /// Remove the components of `bundle` from `entity`. /// - /// SAFETY: + /// # Safety /// - A `BundleInfo` with the corresponding `BundleId` must have been initialized. #[allow(clippy::too_many_arguments)] unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation { @@ -1470,7 +1519,8 @@ impl<'w> EntityWorldMut<'w> { } } -/// SAFETY: all components in the archetype must exist in world +/// # Safety +/// All components in the archetype must exist in world unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( deferred_world: &mut DeferredWorld, archetype: &Archetype, @@ -1491,6 +1541,8 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( } } +const QUERY_MISMATCH_ERROR: &str = "Query does not match the current entity"; + /// A view into a single entity and component in a world, which may either be vacant or occupied. /// /// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. @@ -1876,12 +1928,6 @@ impl<'w> FilteredEntityRef<'w> { self.entity.archetype() } - /// Returns an iterator over the component ids that are accessed by self. - #[inline] - pub fn components(&self) -> impl Iterator + '_ { - self.access.component_reads_and_writes() - } - /// Returns a reference to the underlying [`Access`]. #[inline] pub fn access(&self) -> &Access { @@ -2133,12 +2179,6 @@ impl<'w> FilteredEntityMut<'w> { self.entity.archetype() } - /// Returns an iterator over the component ids that are accessed by self. - #[inline] - pub fn components(&self) -> impl Iterator + '_ { - self.access.component_reads_and_writes() - } - /// Returns a reference to the underlying [`Access`]. #[inline] pub fn access(&self) -> &Access { @@ -2333,6 +2373,184 @@ pub enum TryFromFilteredError { MissingWriteAllAccess, } +/// Provides read-only access to a single entity and all its components, save +/// for an explicitly-enumerated set. +#[derive(Clone)] +pub struct EntityRefExcept<'w, B> +where + B: Bundle, +{ + entity: UnsafeEntityCell<'w>, + phantom: PhantomData, +} + +impl<'w, B> EntityRefExcept<'w, B> +where + B: Bundle, +{ + /// # Safety + /// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`. + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self { + Self { + entity, + phantom: PhantomData, + } + } + + /// Gets access to the component of type `C` for the current entity. Returns + /// `None` if the component doesn't have a component of that type or if the + /// type is one of the excluded components. + #[inline] + pub fn get(&self) -> Option<&'w C> + where + C: Component, + { + let components = self.entity.world().components(); + let id = components.component_id::()?; + if bundle_contains_component::(components, id) { + None + } else { + // SAFETY: We have read access for all components that weren't + // covered by the `contains` check above. + unsafe { self.entity.get() } + } + } + + /// Gets access to the component of type `C` for the current entity, + /// including change detection information. Returns `None` if the component + /// doesn't have a component of that type or if the type is one of the + /// excluded components. + #[inline] + pub fn get_ref(&self) -> Option> + where + C: Component, + { + let components = self.entity.world().components(); + let id = components.component_id::()?; + if bundle_contains_component::(components, id) { + None + } else { + // SAFETY: We have read access for all components that weren't + // covered by the `contains` check above. + unsafe { self.entity.get_ref() } + } + } +} + +impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B> +where + B: Bundle, +{ + fn from(entity_mut: &'a EntityMutExcept<'_, B>) -> Self { + // SAFETY: All accesses that `EntityRefExcept` provides are also + // accesses that `EntityMutExcept` provides. + unsafe { EntityRefExcept::new(entity_mut.entity) } + } +} + +/// Provides mutable access to all components of an entity, with the exception +/// of an explicit set. +/// +/// This is a rather niche type that should only be used if you need access to +/// *all* components of an entity, while still allowing you to consult other +/// queries that might match entities that this query also matches. If you don't +/// need access to all components, prefer a standard query with a +/// [`crate::query::Without`] filter. +#[derive(Clone)] +pub struct EntityMutExcept<'w, B> +where + B: Bundle, +{ + entity: UnsafeEntityCell<'w>, + phantom: PhantomData, +} + +impl<'w, B> EntityMutExcept<'w, B> +where + B: Bundle, +{ + /// # Safety + /// Other users of `UnsafeEntityCell` must not have access to any components not in `B`. + pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self { + Self { + entity, + phantom: PhantomData, + } + } + + /// Returns a new instance with a shorter lifetime. + /// + /// This is useful if you have `&mut EntityMutExcept`, but you need + /// `EntityMutExcept`. + pub fn reborrow(&mut self) -> EntityMutExcept<'_, B> { + // SAFETY: We have exclusive access to the entire entity and the + // applicable components. + unsafe { Self::new(self.entity) } + } + + /// Gets read-only access to all of the entity's components, except for the + /// ones in `CL`. + #[inline] + pub fn as_readonly(&self) -> EntityRefExcept<'_, B> { + EntityRefExcept::from(self) + } + + /// Gets access to the component of type `C` for the current entity. Returns + /// `None` if the component doesn't have a component of that type or if the + /// type is one of the excluded components. + #[inline] + pub fn get(&self) -> Option<&'_ C> + where + C: Component, + { + self.as_readonly().get() + } + + /// Gets access to the component of type `C` for the current entity, + /// including change detection information. Returns `None` if the component + /// doesn't have a component of that type or if the type is one of the + /// excluded components. + #[inline] + pub fn get_ref(&self) -> Option> + where + C: Component, + { + self.as_readonly().get_ref() + } + + /// Gets mutable access to the component of type `C` for the current entity. + /// Returns `None` if the component doesn't have a component of that type or + /// if the type is one of the excluded components. + #[inline] + pub fn get_mut(&mut self) -> Option> + where + C: Component, + { + let components = self.entity.world().components(); + let id = components.component_id::()?; + if bundle_contains_component::(components, id) { + None + } else { + // SAFETY: We have write access for all components that weren't + // covered by the `contains` check above. + unsafe { self.entity.get_mut() } + } + } +} + +fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool +where + B: Bundle, +{ + let mut found = false; + B::get_component_ids(components, &mut |maybe_id| { + if let Some(id) = maybe_id { + found = found || id == query_id; + } + }); + found +} + /// Inserts a dynamic [`Bundle`] into the entity. /// /// # Safety @@ -2527,16 +2745,11 @@ pub(crate) unsafe fn take_component<'a>( match component_info.storage_type() { StorageType::Table => { let table = &mut storages.tables[location.table_id]; - let components = table.get_column_mut(component_id).unwrap(); // SAFETY: // - archetypes only store valid table_rows // - index is in bounds as promised by caller // - promote is safe because the caller promises to remove the table row without dropping it immediately afterwards - unsafe { - components - .get_data_unchecked_mut(location.table_row) - .promote() - } + unsafe { table.take_component(component_id, location.table_row) } } StorageType::SparseSet => storages .sparse_sets @@ -2552,9 +2765,12 @@ mod tests { use bevy_ptr::OwningPtr; use std::panic::AssertUnwindSafe; + use crate::system::RunSystemOnce as _; use crate::world::{FilteredEntityMut, FilteredEntityRef}; use crate::{self as bevy_ecs, component::ComponentId, prelude::*, system::assert_is_system}; + use super::{EntityMutExcept, EntityRefExcept}; + #[test] fn sorted_remove() { let mut a = vec![1, 2, 3, 4, 5, 6, 7]; @@ -2947,6 +3163,164 @@ mod tests { world.spawn_empty().remove_by_id(test_component_id); } + /// Tests that components can be accessed through an `EntityRefExcept`. + #[test] + fn entity_ref_except() { + let mut world = World::new(); + world.init_component::(); + world.init_component::(); + + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + let mut query = world.query::>(); + + let mut found = false; + for entity_ref in query.iter_mut(&mut world) { + found = true; + assert!(entity_ref.get::().is_none()); + assert!(entity_ref.get_ref::().is_none()); + assert!(matches!( + entity_ref.get::(), + Some(TestComponent2(0)) + )); + } + + assert!(found); + } + + // Test that a single query can't both contain a mutable reference to a + // component C and an `EntityRefExcept` that doesn't include C among its + // exclusions. + #[test] + #[should_panic] + fn entity_ref_except_conflicts_with_self() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + // This should panic, because we have a mutable borrow on + // `TestComponent` but have a simultaneous indirect immutable borrow on + // that component via `EntityRefExcept`. + world.run_system_once(system); + + fn system(_: Query<(&mut TestComponent, EntityRefExcept)>) {} + } + + // Test that an `EntityRefExcept` that doesn't include a component C among + // its exclusions can't coexist with a mutable query for that component. + #[test] + #[should_panic] + fn entity_ref_except_conflicts_with_other() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + // This should panic, because we have a mutable borrow on + // `TestComponent` but have a simultaneous indirect immutable borrow on + // that component via `EntityRefExcept`. + world.run_system_once(system); + + fn system(_: Query<&mut TestComponent>, _: Query>) {} + } + + // Test that an `EntityRefExcept` with an exception for some component C can + // coexist with a query for that component C. + #[test] + fn entity_ref_except_doesnt_conflict() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + world.run_system_once(system); + + fn system(_: Query<&mut TestComponent>, query: Query>) { + for entity_ref in query.iter() { + assert!(matches!( + entity_ref.get::(), + Some(TestComponent2(0)) + )); + } + } + } + + /// Tests that components can be mutably accessed through an + /// `EntityMutExcept`. + #[test] + fn entity_mut_except() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + let mut query = world.query::>(); + + let mut found = false; + for mut entity_mut in query.iter_mut(&mut world) { + found = true; + assert!(entity_mut.get::().is_none()); + assert!(entity_mut.get_ref::().is_none()); + assert!(entity_mut.get_mut::().is_none()); + assert!(matches!( + entity_mut.get::(), + Some(TestComponent2(0)) + )); + } + + assert!(found); + } + + // Test that a single query can't both contain a mutable reference to a + // component C and an `EntityMutExcept` that doesn't include C among its + // exclusions. + #[test] + #[should_panic] + fn entity_mut_except_conflicts_with_self() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + // This should panic, because we have a mutable borrow on + // `TestComponent` but have a simultaneous indirect immutable borrow on + // that component via `EntityRefExcept`. + world.run_system_once(system); + + fn system(_: Query<(&mut TestComponent, EntityMutExcept)>) {} + } + + // Test that an `EntityMutExcept` that doesn't include a component C among + // its exclusions can't coexist with a query for that component. + #[test] + #[should_panic] + fn entity_mut_except_conflicts_with_other() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + // This should panic, because we have a mutable borrow on + // `TestComponent` but have a simultaneous indirect immutable borrow on + // that component via `EntityRefExcept`. + world.run_system_once(system); + + fn system(_: Query<&mut TestComponent>, mut query: Query>) { + for mut entity_mut in query.iter_mut() { + assert!(entity_mut + .get_mut::() + .is_some_and(|component| component.0 == 0)); + } + } + } + + // Test that an `EntityMutExcept` with an exception for some component C can + // coexist with a query for that component C. + #[test] + fn entity_mut_except_doesnt_conflict() { + let mut world = World::new(); + world.spawn(TestComponent(0)).insert(TestComponent2(0)); + + world.run_system_once(system); + + fn system(_: Query<&mut TestComponent>, mut query: Query>) { + for mut entity_mut in query.iter_mut() { + assert!(entity_mut + .get_mut::() + .is_some_and(|component| component.0 == 0)); + } + } + } + #[derive(Component)] struct A; @@ -3115,4 +3489,24 @@ mod tests { assert!(e.get_mut_by_id(a_id).is_none()); assert!(e.get_change_ticks_by_id(a_id).is_none()); } + + #[test] + fn get_components() { + #[derive(Component, PartialEq, Eq, Debug)] + struct X(usize); + + #[derive(Component, PartialEq, Eq, Debug)] + struct Y(usize); + let mut world = World::default(); + let e1 = world.spawn((X(7), Y(10))).id(); + let e2 = world.spawn(X(8)).id(); + let e3 = world.spawn_empty().id(); + + assert_eq!( + Some((&X(7), &Y(10))), + world.entity(e1).get_components::<(&X, &Y)>() + ); + assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>()); + assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>()); + } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 16486885b2..2c55062751 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -19,8 +19,8 @@ pub use crate::{ pub use component_constants::*; pub use deferred_world::DeferredWorld; pub use entity_ref::{ - EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef, - OccupiedEntry, VacantEntry, + EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry, + FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry, }; pub use identifier::WorldId; pub use spawn_batch::*; @@ -61,7 +61,7 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; /// A [`World`] mutation. /// -/// Should be used with [`Commands::add`]. +/// Should be used with [`Commands::queue`]. /// /// # Usage /// @@ -83,7 +83,7 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; /// } /// /// fn some_system(mut commands: Commands) { -/// commands.add(AddToCounter(42)); +/// commands.queue(AddToCounter(42)); /// } /// ``` pub trait Command: Send + 'static { @@ -162,6 +162,8 @@ impl Drop for World { drop(unsafe { Box::from_raw(self.command_queue.bytes.as_ptr()) }); // SAFETY: Pointers in internal command queue are only invalidated here drop(unsafe { Box::from_raw(self.command_queue.cursor.as_ptr()) }); + // SAFETY: Pointers in internal command queue are only invalidated here + drop(unsafe { Box::from_raw(self.command_queue.panic_recovery.as_ptr()) }); } } @@ -2500,7 +2502,7 @@ impl World { /// total += info.layout().size(); /// } /// println!("Total size: {} bytes", total); - /// # assert_eq!(total, std::mem::size_of::() + std::mem::size_of::()); + /// # assert_eq!(total, size_of::() + size_of::()); /// ``` /// /// ## Dynamically running closures for resources matching specific `TypeId`s diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 00b5349d80..f1af0c11c2 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -11,8 +11,9 @@ use crate::{ entity::{Entities, Entity, EntityLocation}, observer::Observers, prelude::Component, + query::{DebugCheckedUnwrap, ReadOnlyQueryData}, removal_detection::RemovedComponentEvents, - storage::{Column, ComponentSparseSet, Storages}, + storage::{ComponentSparseSet, Storages, Table}, system::{Res, Resource}, world::RawCommandQueue, }; @@ -247,7 +248,7 @@ impl<'w> UnsafeWorldCell<'w> { } /// Retrieves this world's [`Observers`] collection. - pub(crate) unsafe fn observers(self) -> &'w Observers { + pub(crate) fn observers(self) -> &'w Observers { // SAFETY: // - we only access world metadata &unsafe { self.world_metadata() }.observers @@ -882,6 +883,55 @@ impl<'w> UnsafeEntityCell<'w> { }) } } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably + /// - no mutable references to the queried data exist at the same time + pub(crate) unsafe fn get_components(&self) -> Option> { + // SAFETY: World is only used to access query data and initialize query state + let state = unsafe { + let world = self.world().world(); + Q::get_state(world.components())? + }; + let location = self.location(); + // SAFETY: Location is guaranteed to exist + let archetype = unsafe { + self.world + .archetypes() + .get(location.archetype_id) + .debug_checked_unwrap() + }; + if Q::matches_component_set(&state, &|id| archetype.contains(id)) { + // SAFETY: state was initialized above using the world passed into this function + let mut fetch = unsafe { + Q::init_fetch( + self.world, + &state, + self.world.last_change_tick(), + self.world.change_tick(), + ) + }; + // SAFETY: Table is guaranteed to exist + let table = unsafe { + self.world + .storages() + .tables + .get(location.table_id) + .debug_checked_unwrap() + }; + // SAFETY: Archetype and table are from the same world used to initialize state and fetch. + // Table corresponds to archetype. State is the same state used to init fetch above. + unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) } + // SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist. + unsafe { Some(Q::fetch(&mut fetch, self.id(), location.table_row)) } + } else { + None + } + } } impl<'w> UnsafeEntityCell<'w> { @@ -952,22 +1002,18 @@ impl<'w> UnsafeEntityCell<'w> { impl<'w> UnsafeWorldCell<'w> { #[inline] - /// # Safety: - /// - the returned `Column` is only used in ways that this [`UnsafeWorldCell`] has permission for. - /// - the returned `Column` is only used in ways that would not conflict with any existing - /// borrows of world data. - unsafe fn fetch_table( - self, - location: EntityLocation, - component_id: ComponentId, - ) -> Option<&'w Column> { - // SAFETY: caller ensures returned data is not misused and we have not created any borrows - // of component/resource data - unsafe { self.storages() }.tables[location.table_id].get_column(component_id) + /// # Safety + /// - the returned `Table` is only used in ways that this [`UnsafeWorldCell`] has permission for. + /// - the returned `Table` is only used in ways that would not conflict with any existing borrows of world data. + unsafe fn fetch_table(self, location: EntityLocation) -> Option<&'w Table> { + // SAFETY: + // - caller ensures returned data is not misused and we have not created any borrows of component/resource data + // - `location` contains a valid `TableId`, so getting the table won't fail + unsafe { self.storages().tables.get(location.table_id) } } #[inline] - /// # Safety: + /// # Safety /// - the returned `ComponentSparseSet` is only used in ways that this [`UnsafeWorldCell`] has permission for. /// - the returned `ComponentSparseSet` is only used in ways that would not conflict with any existing /// borrows of world data. @@ -998,9 +1044,9 @@ unsafe fn get_component( // SAFETY: component_id exists and is therefore valid match storage_type { StorageType::Table => { - let components = world.fetch_table(location, component_id)?; + let table = world.fetch_table(location)?; // SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules - Some(components.get_data_unchecked(location.table_row)) + table.get_component(component_id, location.table_row) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get(entity), } @@ -1024,17 +1070,23 @@ unsafe fn get_component_and_ticks( ) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { match storage_type { StorageType::Table => { - let components = world.fetch_table(location, component_id)?; + let table = world.fetch_table(location)?; // SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules Some(( - components.get_data_unchecked(location.table_row), + table.get_component(component_id, location.table_row)?, TickCells { - added: components.get_added_tick_unchecked(location.table_row), - changed: components.get_changed_tick_unchecked(location.table_row), + added: table + .get_added_tick(component_id, location.table_row) + .debug_checked_unwrap(), + changed: table + .get_changed_tick(component_id, location.table_row) + .debug_checked_unwrap(), }, #[cfg(feature = "track_change_detection")] - components.get_changed_by_unchecked(location.table_row), + table + .get_changed_by(component_id, location.table_row) + .debug_checked_unwrap(), #[cfg(not(feature = "track_change_detection"))] (), )) @@ -1062,9 +1114,9 @@ unsafe fn get_ticks( ) -> Option { match storage_type { StorageType::Table => { - let components = world.fetch_table(location, component_id)?; + let table = world.fetch_table(location)?; // SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules - Some(components.get_ticks_unchecked(location.table_row)) + table.get_ticks_unchecked(component_id, location.table_row) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity), } diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 21b4b83e99..b2b037e19e 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -17,7 +17,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } # other -gilrs = "0.10.1" +gilrs = "0.11.0" thiserror = "1.0" [lints] diff --git a/crates/bevy_gizmos/src/aabb.rs b/crates/bevy_gizmos/src/aabb.rs index 6496dc9125..b10b2bc946 100644 --- a/crates/bevy_gizmos/src/aabb.rs +++ b/crates/bevy_gizmos/src/aabb.rs @@ -64,7 +64,7 @@ pub struct AabbGizmoConfigGroup { /// Add this [`Component`] to an entity to draw its [`Aabb`] component. #[derive(Component, Reflect, Default, Debug)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct ShowAabbGizmo { /// The color of the box. /// diff --git a/crates/bevy_gizmos/src/arcs.rs b/crates/bevy_gizmos/src/arcs.rs index 7164a6e79f..c3b96f75dc 100644 --- a/crates/bevy_gizmos/src/arcs.rs +++ b/crates/bevy_gizmos/src/arcs.rs @@ -114,8 +114,7 @@ fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator impl Iterator { (0..resolution + 1).map(move |i| { let angle = i as f32 * TAU / resolution as f32; - let (x, y) = angle.sin_cos(); + let (x, y) = ops::sin_cos(angle); Vec2::new(x, y) * half_size }) } diff --git a/crates/bevy_gizmos/src/grid.rs b/crates/bevy_gizmos/src/grid.rs index 8c387fa349..7e637c54e1 100644 --- a/crates/bevy_gizmos/src/grid.rs +++ b/crates/bevy_gizmos/src/grid.rs @@ -6,7 +6,7 @@ use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; use bevy_math::Vec3Swizzles; -use bevy_math::{Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3}; +use bevy_math::{ops, Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3}; /// A builder returned by [`Gizmos::grid_3d`] pub struct GridBuilder3d<'a, 'w, 's, Config, Clear> @@ -374,7 +374,7 @@ fn draw_grid( } // Offset between two adjacent grid cells along the x/y-axis and accounting for skew. - let skew_tan = Vec3::from(skew.to_array().map(f32::tan)); + let skew_tan = Vec3::from(skew.to_array().map(ops::tan)); let dx = or_zero( cell_count.x != 0, spacing.x * Vec3::new(1., skew_tan.y, skew_tan.z), diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs index 6155b9e6a0..0debc8238c 100644 --- a/crates/bevy_gizmos/src/light.rs +++ b/crates/bevy_gizmos/src/light.rs @@ -18,6 +18,7 @@ use bevy_ecs::{ system::{Query, Res}, }; use bevy_math::{ + ops, primitives::{Cone, Sphere}, Isometry3d, Quat, Vec3, }; @@ -78,12 +79,12 @@ fn spot_light_gizmo( // Offset the tip of the cone to the light position. for angle in [spot_light.inner_angle, spot_light.outer_angle] { - let height = spot_light.range * angle.cos(); + let height = spot_light.range * ops::cos(angle); let position = translation + rotation * Vec3::NEG_Z * height / 2.0; gizmos .primitive_3d( &Cone { - radius: spot_light.range * angle.sin(), + radius: spot_light.range * ops::sin(angle), height, }, Isometry3d::new(position, rotation * Quat::from_rotation_x(PI / 2.0)), @@ -200,7 +201,7 @@ impl Default for LightGizmoConfigGroup { /// Add this [`Component`] to an entity to draw any of its lights components /// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]). #[derive(Component, Reflect, Default, Debug)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct ShowLightGizmo { /// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`]. /// diff --git a/crates/bevy_gizmos/src/primitives/helpers.rs b/crates/bevy_gizmos/src/primitives/helpers.rs index 864c4bd0dc..fa86a6ebe0 100644 --- a/crates/bevy_gizmos/src/primitives/helpers.rs +++ b/crates/bevy_gizmos/src/primitives/helpers.rs @@ -1,6 +1,6 @@ use std::f32::consts::TAU; -use bevy_math::Vec2; +use bevy_math::{ops, Vec2}; /// Calculates the `nth` coordinate of a circle. /// @@ -9,7 +9,7 @@ use bevy_math::Vec2; /// and proceeds counter-clockwise. pub(crate) fn single_circle_coordinate(radius: f32, resolution: u32, nth_point: u32) -> Vec2 { let angle = nth_point as f32 * TAU / resolution as f32; - let (x, y) = angle.sin_cos(); + let (x, y) = ops::sin_cos(angle); Vec2::new(x, y) * radius } diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index bca24f0af7..d27a899b76 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -107,6 +107,7 @@ use bevy_app::prelude::*; use bevy_asset::{Asset, AssetApp, AssetPath, Handle}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_pbr::StandardMaterial; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypePath}; use bevy_render::{ mesh::{skinning::SkinnedMeshInverseBindposes, Mesh, MeshVertexAttribute}, @@ -422,7 +423,7 @@ impl GltfPrimitive { /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct GltfExtras { /// Content of the extra data. pub value: String, @@ -432,7 +433,7 @@ pub struct GltfExtras { /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct GltfSceneExtras { /// Content of the extra data. pub value: String, @@ -442,7 +443,7 @@ pub struct GltfSceneExtras { /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct GltfMeshExtras { /// Content of the extra data. pub value: String, @@ -452,7 +453,7 @@ pub struct GltfMeshExtras { /// /// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras). #[derive(Clone, Debug, Reflect, Default, Component)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct GltfMaterialExtras { /// Content of the extra data. pub value: String, diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 010e80f659..88a1964370 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1254,9 +1254,8 @@ fn load_node( let orthographic_projection = OrthographicProjection { near: orthographic.znear(), far: orthographic.zfar(), - scaling_mode: ScalingMode::FixedHorizontal(1.0), - scale: xmag, - ..Default::default() + scaling_mode: ScalingMode::FixedHorizontal(xmag), + ..OrthographicProjection::default_3d() }; Projection::Orthographic(orthographic_projection) diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 90ea42af39..0c9d03ce77 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -19,7 +19,7 @@ fn push_events(world: &mut World, events: impl IntoIterator() { children.0.push(child); @@ -161,14 +161,14 @@ fn clear_children(parent: Entity, world: &mut World) { /// Command that adds a child to an entity. #[derive(Debug)] -pub struct PushChild { +pub struct AddChild { /// Parent entity to add the child to. pub parent: Entity, /// Child entity to add. pub child: Entity, } -impl Command for PushChild { +impl Command for AddChild { fn apply(self, world: &mut World) { world.entity_mut(self.parent).add_child(self.child); } @@ -192,14 +192,14 @@ impl Command for InsertChildren { /// Command that pushes children to the end of the entity's [`Children`]. #[derive(Debug)] -pub struct PushChildren { +pub struct AddChildren { parent: Entity, children: SmallVec<[Entity; 8]>, } -impl Command for PushChildren { +impl Command for AddChildren { fn apply(self, world: &mut World) { - world.entity_mut(self.parent).push_children(&self.children); + world.entity_mut(self.parent).add_children(&self.children); } } @@ -236,7 +236,7 @@ pub struct ReplaceChildren { impl Command for ReplaceChildren { fn apply(self, world: &mut World) { clear_children(self.parent, world); - world.entity_mut(self.parent).push_children(&self.children); + world.entity_mut(self.parent).add_children(&self.children); } } @@ -276,7 +276,7 @@ impl Command for RemoveParent { /// ``` pub struct ChildBuilder<'a> { commands: Commands<'a, 'a>, - push_children: PushChildren, + add_children: AddChildren, } /// Trait for building children entities and adding them to a parent entity. This is used in @@ -304,8 +304,8 @@ pub trait ChildBuild { /// Returns the parent entity. fn parent_entity(&self) -> Entity; - /// Adds a command to be executed, like [`Commands::add`]. - fn add_command(&mut self, command: C) -> &mut Self; + /// Adds a command to be executed, like [`Commands::queue`]. + fn enqueue_command(&mut self, command: C) -> &mut Self; } impl ChildBuild for ChildBuilder<'_> { @@ -313,22 +313,22 @@ impl ChildBuild for ChildBuilder<'_> { fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands { let e = self.commands.spawn(bundle); - self.push_children.children.push(e.id()); + self.add_children.children.push(e.id()); e } fn spawn_empty(&mut self) -> EntityCommands { let e = self.commands.spawn_empty(); - self.push_children.children.push(e.id()); + self.add_children.children.push(e.id()); e } fn parent_entity(&self) -> Entity { - self.push_children.parent + self.add_children.parent } - fn add_command(&mut self, command: C) -> &mut Self { - self.commands.add(command); + fn enqueue_command(&mut self, command: C) -> &mut Self { + self.commands.queue(command); self } } @@ -362,7 +362,7 @@ pub trait BuildChildren { /// # Panics /// /// Panics if any of the children are the same as the parent. - fn push_children(&mut self, children: &[Entity]) -> &mut Self; + fn add_children(&mut self, children: &[Entity]) -> &mut Self; /// Inserts children at the given index. /// @@ -428,34 +428,34 @@ impl BuildChildren for EntityCommands<'_> { let parent = self.id(); let mut builder = ChildBuilder { commands: self.commands(), - push_children: PushChildren { + add_children: AddChildren { children: SmallVec::default(), parent, }, }; spawn_children(&mut builder); - let children = builder.push_children; + let children = builder.add_children; if children.children.contains(&parent) { panic!("Entity cannot be a child of itself."); } - self.commands().add(children); + self.commands().queue(children); self } fn with_child(&mut self, bundle: B) -> &mut Self { let parent = self.id(); let child = self.commands().spawn(bundle).id(); - self.commands().add(PushChild { parent, child }); + self.commands().queue(AddChild { parent, child }); self } - fn push_children(&mut self, children: &[Entity]) -> &mut Self { + fn add_children(&mut self, children: &[Entity]) -> &mut Self { let parent = self.id(); if children.contains(&parent) { panic!("Cannot push entity as a child of itself."); } - self.commands().add(PushChildren { + self.commands().queue(AddChildren { children: SmallVec::from(children), parent, }); @@ -467,7 +467,7 @@ impl BuildChildren for EntityCommands<'_> { if children.contains(&parent) { panic!("Cannot insert entity as a child of itself."); } - self.commands().add(InsertChildren { + self.commands().queue(InsertChildren { children: SmallVec::from(children), index, parent, @@ -477,7 +477,7 @@ impl BuildChildren for EntityCommands<'_> { fn remove_children(&mut self, children: &[Entity]) -> &mut Self { let parent = self.id(); - self.commands().add(RemoveChildren { + self.commands().queue(RemoveChildren { children: SmallVec::from(children), parent, }); @@ -489,13 +489,13 @@ impl BuildChildren for EntityCommands<'_> { if child == parent { panic!("Cannot add entity as a child of itself."); } - self.commands().add(PushChild { child, parent }); + self.commands().queue(AddChild { child, parent }); self } fn clear_children(&mut self) -> &mut Self { let parent = self.id(); - self.commands().add(ClearChildren { parent }); + self.commands().queue(ClearChildren { parent }); self } @@ -504,7 +504,7 @@ impl BuildChildren for EntityCommands<'_> { if children.contains(&parent) { panic!("Cannot replace entity as a child of itself."); } - self.commands().add(ReplaceChildren { + self.commands().queue(ReplaceChildren { children: SmallVec::from(children), parent, }); @@ -516,13 +516,13 @@ impl BuildChildren for EntityCommands<'_> { if child == parent { panic!("Cannot set parent to itself"); } - self.commands().add(PushChild { child, parent }); + self.commands().queue(AddChild { child, parent }); self } fn remove_parent(&mut self) -> &mut Self { let child = self.id(); - self.commands().add(RemoveParent { child }); + self.commands().queue(RemoveParent { child }); self } } @@ -539,7 +539,7 @@ impl ChildBuild for WorldChildBuilder<'_> { fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut { let entity = self.world.spawn((bundle, Parent(self.parent))).id(); - push_child_unchecked(self.world, self.parent, entity); + add_child_unchecked(self.world, self.parent, entity); push_events( self.world, [HierarchyEvent::ChildAdded { @@ -552,7 +552,7 @@ impl ChildBuild for WorldChildBuilder<'_> { fn spawn_empty(&mut self) -> EntityWorldMut { let entity = self.world.spawn(Parent(self.parent)).id(); - push_child_unchecked(self.world, self.parent, entity); + add_child_unchecked(self.world, self.parent, entity); push_events( self.world, [HierarchyEvent::ChildAdded { @@ -567,7 +567,7 @@ impl ChildBuild for WorldChildBuilder<'_> { self.parent } - fn add_command(&mut self, command: C) -> &mut Self { + fn enqueue_command(&mut self, command: C) -> &mut Self { command.apply(self.world); self } @@ -613,7 +613,7 @@ impl BuildChildren for EntityWorldMut<'_> { self } - fn push_children(&mut self, children: &[Entity]) -> &mut Self { + fn add_children(&mut self, children: &[Entity]) -> &mut Self { if children.is_empty() { return self; } @@ -691,7 +691,7 @@ impl BuildChildren for EntityWorldMut<'_> { } fn replace_children(&mut self, children: &[Entity]) -> &mut Self { - self.clear_children().push_children(children) + self.clear_children().add_children(children) } } @@ -838,7 +838,7 @@ mod tests { let [a, b, c] = std::array::from_fn(|_| world.spawn_empty().id()); - world.entity_mut(a).push_children(&[b, c]); + world.entity_mut(a).add_children(&[b, c]); world.entity_mut(b).remove_parent(); assert_parent(world, b, None); @@ -920,7 +920,7 @@ mod tests { let mut queue = CommandQueue::default(); { let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).push_children(&entities[1..3]); + commands.entity(entities[0]).add_children(&entities[1..3]); } queue.apply(&mut world); @@ -981,7 +981,7 @@ mod tests { let mut queue = CommandQueue::default(); { let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).push_children(&entities[1..3]); + commands.entity(entities[0]).add_children(&entities[1..3]); } queue.apply(&mut world); @@ -1019,7 +1019,7 @@ mod tests { let mut queue = CommandQueue::default(); { let mut commands = Commands::new(&mut queue, &world); - commands.entity(entities[0]).push_children(&entities[1..3]); + commands.entity(entities[0]).add_children(&entities[1..3]); } queue.apply(&mut world); @@ -1060,7 +1060,7 @@ mod tests { .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) .collect::>(); - world.entity_mut(entities[0]).push_children(&entities[1..3]); + world.entity_mut(entities[0]).add_children(&entities[1..3]); let parent = entities[0]; let child1 = entities[1]; @@ -1103,7 +1103,7 @@ mod tests { .spawn_batch(vec![C(1), C(2), C(3)]) .collect::>(); - world.entity_mut(entities[0]).push_children(&entities[1..3]); + world.entity_mut(entities[0]).add_children(&entities[1..3]); let parent = entities[0]; let child1 = entities[1]; @@ -1130,7 +1130,7 @@ mod tests { .spawn_batch(vec![C(1), C(2), C(3), C(4), C(5)]) .collect::>(); - world.entity_mut(entities[0]).push_children(&entities[1..3]); + world.entity_mut(entities[0]).add_children(&entities[1..3]); let parent = entities[0]; let child1 = entities[1]; @@ -1170,15 +1170,15 @@ mod tests { let parent2 = entities[1]; let child = entities[2]; - // push child into parent1 - world.entity_mut(parent1).push_children(&[child]); + // add child into parent1 + world.entity_mut(parent1).add_children(&[child]); assert_eq!( world.get::(parent1).unwrap().0.as_slice(), &[child] ); - // move only child from parent1 with `push_children` - world.entity_mut(parent2).push_children(&[child]); + // move only child from parent1 with `add_children` + world.entity_mut(parent2).add_children(&[child]); assert!(world.get::(parent1).is_none()); // move only child from parent2 with `insert_children` @@ -1204,10 +1204,10 @@ mod tests { let mut queue = CommandQueue::default(); - // push child into parent1 + // add child into parent1 { let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent1).push_children(&[child]); + commands.entity(parent1).add_children(&[child]); queue.apply(&mut world); } assert_eq!( @@ -1215,10 +1215,10 @@ mod tests { &[child] ); - // move only child from parent1 with `push_children` + // move only child from parent1 with `add_children` { let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent2).push_children(&[child]); + commands.entity(parent2).add_children(&[child]); queue.apply(&mut world); } assert!(world.get::(parent1).is_none()); @@ -1249,20 +1249,20 @@ mod tests { } #[test] - fn regression_push_children_same_archetype() { + fn regression_add_children_same_archetype() { let mut world = World::new(); let child = world.spawn_empty().id(); - world.spawn_empty().push_children(&[child]); + world.spawn_empty().add_children(&[child]); } #[test] - fn push_children_idempotent() { + fn add_children_idempotent() { let mut world = World::new(); let child = world.spawn_empty().id(); let parent = world .spawn_empty() - .push_children(&[child]) - .push_children(&[child]) + .add_children(&[child]) + .add_children(&[child]) .id(); let mut query = world.query::<&Children>(); @@ -1271,9 +1271,9 @@ mod tests { } #[test] - fn push_children_does_not_insert_empty_children() { + fn add_children_does_not_insert_empty_children() { let mut world = World::new(); - let parent = world.spawn_empty().push_children(&[]).id(); + let parent = world.spawn_empty().add_children(&[]).id(); let mut query = world.query::<&Children>(); let children = query.get(&world, parent); diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index 5a53462d82..ee2fb5578a 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -1,5 +1,5 @@ #[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ReflectComponent, ReflectMapEntities}; +use bevy_ecs::reflect::{ReflectComponent, ReflectFromWorld, ReflectMapEntities}; use bevy_ecs::{ component::Component, entity::{Entity, EntityMapper, MapEntities}, @@ -25,7 +25,7 @@ use std::ops::Deref; /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children #[derive(Component, Debug)] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr(feature = "reflect", reflect(Component, MapEntities))] +#[cfg_attr(feature = "reflect", reflect(Component, MapEntities, Debug, FromWorld))] pub struct Children(pub(crate) SmallVec<[Entity; 8]>); impl MapEntities for Children { diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 99d8e45460..ce7e061ab4 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -1,5 +1,5 @@ #[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ReflectComponent, ReflectMapEntities}; +use bevy_ecs::reflect::{ReflectComponent, ReflectFromWorld, ReflectMapEntities}; use bevy_ecs::{ component::Component, entity::{Entity, EntityMapper, MapEntities}, @@ -23,7 +23,10 @@ use std::ops::Deref; /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children #[derive(Component, Debug, Eq, PartialEq)] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr(feature = "reflect", reflect(Component, MapEntities, PartialEq))] +#[cfg_attr( + feature = "reflect", + reflect(Component, MapEntities, PartialEq, Debug, FromWorld) +)] pub struct Parent(pub(crate) Entity); impl Parent { diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index d3db4dd4d8..446f3884ef 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -94,12 +94,12 @@ impl DespawnRecursiveExt for EntityCommands<'_> { /// This will emit warnings for any entity that does not exist. fn despawn_recursive(mut self) { let entity = self.id(); - self.commands().add(DespawnRecursive { entity }); + self.commands().queue(DespawnRecursive { entity }); } fn despawn_descendants(&mut self) -> &mut Self { let entity = self.id(); - self.commands().add(DespawnChildrenRecursive { entity }); + self.commands().queue(DespawnChildrenRecursive { entity }); self } } diff --git a/crates/bevy_hierarchy/src/query_extension.rs b/crates/bevy_hierarchy/src/query_extension.rs index 4525a3ea5d..de342846a7 100644 --- a/crates/bevy_hierarchy/src/query_extension.rs +++ b/crates/bevy_hierarchy/src/query_extension.rs @@ -172,8 +172,8 @@ mod tests { let [a, b, c, d] = std::array::from_fn(|i| world.spawn(A(i)).id()); - world.entity_mut(a).push_children(&[b, c]); - world.entity_mut(c).push_children(&[d]); + world.entity_mut(a).add_children(&[b, c]); + world.entity_mut(c).add_children(&[d]); let mut system_state = SystemState::<(Query<&Children>, Query<&A>)>::new(world); let (children_query, a_query) = system_state.get(world); @@ -191,8 +191,8 @@ mod tests { let [a, b, c] = std::array::from_fn(|i| world.spawn(A(i)).id()); - world.entity_mut(a).push_children(&[b]); - world.entity_mut(b).push_children(&[c]); + world.entity_mut(a).add_children(&[b]); + world.entity_mut(b).add_children(&[c]); let mut system_state = SystemState::<(Query<&Parent>, Query<&A>)>::new(world); let (parent_query, a_query) = system_state.get(world); diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 21cde05f52..b190885723 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use crate::Parent; use bevy_ecs::prelude::*; #[cfg(feature = "bevy_app")] -use bevy_utils::{get_short_name, HashSet}; +use bevy_utils::{HashSet, ShortName}; /// When enabled, runs [`check_hierarchy_component_has_valid_parent`]. /// @@ -67,7 +67,7 @@ pub fn check_hierarchy_component_has_valid_parent( bevy_utils::tracing::warn!( "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004", - ty_name = get_short_name(std::any::type_name::()), + ty_name = ShortName::of::(), name = name.map_or_else(|| format!("Entity {}", entity), |s| format!("The {s} entity")), ); } diff --git a/crates/bevy_input/src/button_input.rs b/crates/bevy_input/src/button_input.rs index 09d6c85a29..3a5d7fd6ae 100644 --- a/crates/bevy_input/src/button_input.rs +++ b/crates/bevy_input/src/button_input.rs @@ -1,5 +1,7 @@ //! The generic input type. +#[cfg(feature = "bevy_reflect")] +use bevy_ecs::reflect::ReflectResource; use bevy_ecs::system::Resource; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -125,7 +127,7 @@ use std::hash::Hash; /// Update, /// something_used.run_if( /// input_just_pressed(KeyCode::KeyE) -/// .or_else(input_just_pressed(GamepadButton::new( +/// .or(input_just_pressed(GamepadButton::new( /// Gamepad::new(0), /// GamepadButtonType::West, /// ))), @@ -154,7 +156,7 @@ use std::hash::Hash; ///[`ResMut`]: bevy_ecs::system::ResMut ///[`DetectChangesMut::bypass_change_detection`]: bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection #[derive(Debug, Clone, Resource)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default))] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default, Resource))] pub struct ButtonInput { /// A collection of every button that is currently being pressed. pressed: HashSet, diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 5e8c78a54c..734492df38 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -2,6 +2,8 @@ use crate::{Axis, ButtonInput, ButtonState}; use bevy_ecs::event::{Event, EventReader, EventWriter}; +#[cfg(feature = "bevy_reflect")] +use bevy_ecs::reflect::ReflectResource; use bevy_ecs::{ change_detection::DetectChangesMut, system::{Res, ResMut, Resource}, @@ -384,7 +386,11 @@ impl GamepadAxis { /// should register as a [`GamepadEvent`]. Events that don't meet the change thresholds defined in [`GamepadSettings`] /// will not register. To modify these settings, mutate the corresponding resource. #[derive(Resource, Default, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Debug, Default, Resource) +)] pub struct GamepadSettings { /// The default button settings. diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 7aadcb50fb..74008dafaf 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -71,7 +71,56 @@ plugin_group! { /// /// [`DefaultPlugins`] contains all the plugins typically required to build /// a *Bevy* application which includes a *window* and presentation components. - /// For *headless* cases – without a *window* or presentation, see [`MinimalPlugins`]. + /// For *headless* cases – without a *window* or presentation, see [`HeadlessPlugins`]. + /// For the absolute minimum number of plugins needed to run a Bevy application, see [`MinimalPlugins`]. +} + +plugin_group! { + /// This plugin group will add all the default plugins for a headless (no *window* or rendering) *Bevy* application: + pub struct HeadlessPlugins { + bevy_app:::PanicHandlerPlugin, + bevy_log:::LogPlugin, + bevy_core:::TaskPoolPlugin, + bevy_core:::TypeRegistrationPlugin, + bevy_core:::FrameCountPlugin, + bevy_time:::TimePlugin, + bevy_transform:::TransformPlugin, + bevy_hierarchy:::HierarchyPlugin, + bevy_diagnostic:::DiagnosticsPlugin, + bevy_app:::ScheduleRunnerPlugin, + #[custom(cfg(not(target_arch = "wasm32")))] + bevy_app:::TerminalCtrlCHandlerPlugin, + #[cfg(feature = "bevy_asset")] + bevy_asset:::AssetPlugin, + #[cfg(feature = "bevy_scene")] + bevy_scene:::ScenePlugin, + #[cfg(feature = "bevy_animation")] + bevy_animation:::AnimationPlugin, + #[cfg(feature = "bevy_state")] + bevy_state::app:::StatesPlugin, + #[cfg(feature = "bevy_ci_testing")] + bevy_dev_tools::ci_testing:::CiTestingPlugin, + #[doc(hidden)] + :IgnoreAmbiguitiesPlugin, + } + /// This group of plugins is intended for use for *headless* programs, for example: dedicated game servers. + /// See the [*Bevy* *headless* example](https://github.com/bevyengine/bevy/blob/main/examples/app/headless.rs) + /// + /// [`HeadlessPlugins`] obeys *Cargo* *feature* flags. Users may exert control over this plugin group + /// by disabling `default-features` in their `Cargo.toml` and enabling only those features + /// that they wish to use. + /// + /// [`HeadlessPlugins`] contains all the plugins typically required to build + /// a *Bevy* application. In contrast with [`DefaultPlugins`], it leaves out *window* and presentation components. + /// This allows applications built using this plugin group to run on devices that do not have a screen or rendering + /// capabilities. + /// It includes a [schedule runner (`ScheduleRunnerPlugin`)](crate::app::ScheduleRunnerPlugin) + /// to provide functionality that would otherwise be driven by a windowed application's + /// *event loop* or *message loop*. + /// + /// Windowed applications that wish to use a reduced set of plugins should consider the + /// [`DefaultPlugins`] plugin group which can be controlled with *Cargo* *feature* flags. + /// For the absolute minimum number of plugins needed to run a Bevy application, see [`MinimalPlugins`]. } #[derive(Default)] @@ -110,12 +159,12 @@ plugin_group! { #[cfg(feature = "bevy_ci_testing")] bevy_dev_tools::ci_testing:::CiTestingPlugin, } - /// This group of plugins is intended for use for minimal, *headless* programs – - /// see the [*Bevy* *headless* example](https://github.com/bevyengine/bevy/blob/main/examples/app/headless.rs) - /// – and includes a [schedule runner (`ScheduleRunnerPlugin`)](crate::app::ScheduleRunnerPlugin) + /// This plugin group represents the absolute minimum, bare-bones, bevy application. + /// Use this if you want to have absolute control over the plugins used. + /// If you are looking to make a *headless* application - without a *window* or rendering, + /// it is usually best to use [`HeadlessPlugins`]. + /// + /// It includes a [schedule runner (`ScheduleRunnerPlugin`)](crate::app::ScheduleRunnerPlugin) /// to provide functionality that would otherwise be driven by a windowed application's /// *event loop* or *message loop*. - /// - /// Windowed applications that wish to use a reduced set of plugins should consider the - /// [`DefaultPlugins`] plugin group which can be controlled with *Cargo* *feature* flags. } diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 812a674ed9..bec80694fd 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -2,7 +2,8 @@ pub use crate::{ app::prelude::*, core::prelude::*, ecs::prelude::*, hierarchy::prelude::*, input::prelude::*, log::prelude::*, math::prelude::*, reflect::prelude::*, time::prelude::*, - transform::prelude::*, utils::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins, + transform::prelude::*, utils::prelude::*, window::prelude::*, DefaultPlugins, HeadlessPlugins, + MinimalPlugins, }; pub use bevy_derive::{bevy_main, Deref, DerefMut}; diff --git a/crates/bevy_math/src/aspect_ratio.rs b/crates/bevy_math/src/aspect_ratio.rs index 97960015a5..5b0fc47946 100644 --- a/crates/bevy_math/src/aspect_ratio.rs +++ b/crates/bevy_math/src/aspect_ratio.rs @@ -1,6 +1,7 @@ //! Provides a simple aspect ratio struct to help with calculations. use crate::Vec2; +use thiserror::Error; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -11,23 +12,74 @@ use bevy_reflect::Reflect; pub struct AspectRatio(f32); impl AspectRatio { - /// Create a new `AspectRatio` from a given `width` and `height`. + /// Standard 16:9 aspect ratio + pub const SIXTEEN_NINE: Self = Self(16.0 / 9.0); + /// Standard 4:3 aspect ratio + pub const FOUR_THREE: Self = Self(4.0 / 3.0); + /// Standard 21:9 ultrawide aspect ratio + pub const ULTRAWIDE: Self = Self(21.0 / 9.0); + + /// Attempts to create a new [`AspectRatio`] from a given width and height. + /// + /// # Errors + /// + /// Returns an `Err` with `AspectRatioError` if: + /// - Either width or height is zero (`AspectRatioError::Zero`) + /// - Either width or height is infinite (`AspectRatioError::Infinite`) + /// - Either width or height is NaN (`AspectRatioError::NaN`) #[inline] - pub fn new(width: f32, height: f32) -> Self { - Self(width / height) + pub fn try_new(width: f32, height: f32) -> Result { + match (width, height) { + (w, h) if w == 0.0 || h == 0.0 => Err(AspectRatioError::Zero), + (w, h) if w.is_infinite() || h.is_infinite() => Err(AspectRatioError::Infinite), + (w, h) if w.is_nan() || h.is_nan() => Err(AspectRatioError::NaN), + _ => Ok(Self(width / height)), + } } - /// Create a new `AspectRatio` from a given amount of `x` pixels and `y` pixels. + /// Attempts to create a new [`AspectRatio`] from a given amount of x pixels and y pixels. #[inline] - pub fn from_pixels(x: u32, y: u32) -> Self { - Self::new(x as f32, y as f32) + pub fn try_from_pixels(x: u32, y: u32) -> Result { + Self::try_new(x as f32, y as f32) + } + + /// Returns the aspect ratio as a f32 value. + #[inline] + pub fn ratio(&self) -> f32 { + self.0 + } + + /// Returns the inverse of this aspect ratio (height/width). + #[inline] + pub fn inverse(&self) -> Self { + Self(1.0 / self.0) + } + + /// Returns true if the aspect ratio represents a landscape orientation. + #[inline] + pub fn is_landscape(&self) -> bool { + self.0 > 1.0 + } + + /// Returns true if the aspect ratio represents a portrait orientation. + #[inline] + pub fn is_portrait(&self) -> bool { + self.0 < 1.0 + } + + /// Returns true if the aspect ratio is exactly square. + #[inline] + pub fn is_square(&self) -> bool { + self.0 == 1.0 } } -impl From for AspectRatio { +impl TryFrom for AspectRatio { + type Error = AspectRatioError; + #[inline] - fn from(value: Vec2) -> Self { - Self::new(value.x, value.y) + fn try_from(value: Vec2) -> Result { + Self::try_new(value.x, value.y) } } @@ -37,3 +89,17 @@ impl From for f32 { aspect_ratio.0 } } + +/// An Error type for when [`super::AspectRatio`] is provided invalid width or height values +#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +pub enum AspectRatioError { + /// Error due to width or height having zero as a value. + #[error("AspectRatio error: width or height is zero")] + Zero, + /// Error due towidth or height being infinite. + #[error("AspectRatio error: width or height is infinite")] + Infinite, + /// Error due to width or height being Not a Number (NaN). + #[error("AspectRatio error: width or height is NaN")] + NaN, +} diff --git a/crates/bevy_math/src/bounding/bounded2d/mod.rs b/crates/bevy_math/src/bounding/bounded2d/mod.rs index 1fdf6c75b0..8db21c94ca 100644 --- a/crates/bevy_math/src/bounding/bounded2d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded2d/mod.rs @@ -2,9 +2,8 @@ mod primitive_impls; use super::{BoundingVolume, IntersectsVolume}; use crate::{ - ops::FloatPow, prelude::{Mat2, Rot2, Vec2}, - Isometry2d, + FloatPow, Isometry2d, }; #[cfg(feature = "bevy_reflect")] diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 70df008694..1020cb114c 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -21,7 +21,7 @@ pub mod curve; mod direction; mod float_ord; mod isometry; -mod ops; +pub mod ops; pub mod primitives; mod ray; mod rects; @@ -36,7 +36,7 @@ pub use common_traits::*; pub use direction::*; pub use float_ord::*; pub use isometry::{Isometry2d, Isometry3d}; -pub use ops::*; +pub use ops::FloatPow; pub use ray::{Ray2d, Ray3d}; pub use rects::*; pub use rotation2d::Rot2; @@ -59,6 +59,7 @@ pub mod prelude { }, curve::*, direction::{Dir2, Dir3, Dir3A}, + ops, primitives::*, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, diff --git a/crates/bevy_math/src/ops.rs b/crates/bevy_math/src/ops.rs index 9a40e8434e..cee52df323 100644 --- a/crates/bevy_math/src/ops.rs +++ b/crates/bevy_math/src/ops.rs @@ -445,6 +445,7 @@ mod libm_ops { #[cfg(feature = "libm")] pub use libm_ops::*; + #[cfg(not(feature = "libm"))] pub use std_ops::*; diff --git a/crates/bevy_math/src/rotation2d.rs b/crates/bevy_math/src/rotation2d.rs index e9fd1f7f13..02f6d34117 100644 --- a/crates/bevy_math/src/rotation2d.rs +++ b/crates/bevy_math/src/rotation2d.rs @@ -1,3 +1,5 @@ +use std::f32::consts::TAU; + use glam::FloatExt; use crate::{ @@ -100,6 +102,26 @@ impl Rot2 { }; /// Creates a [`Rot2`] from a counterclockwise angle in radians. + /// + /// # Note + /// + /// The input rotation will always be clamped to the range `(-π, π]` by design. + /// + /// # Example + /// + /// ``` + /// # use bevy_math::Rot2; + /// # use approx::assert_relative_eq; + /// # use std::f32::consts::{FRAC_PI_2, PI}; + /// + /// let rot1 = Rot2::radians(3.0 * FRAC_PI_2); + /// let rot2 = Rot2::radians(-FRAC_PI_2); + /// assert_relative_eq!(rot1, rot2); + /// + /// let rot3 = Rot2::radians(PI); + /// assert_relative_eq!(rot1 * rot1, rot3); + /// + /// ``` #[inline] pub fn radians(radians: f32) -> Self { let (sin, cos) = ops::sin_cos(radians); @@ -107,11 +129,55 @@ impl Rot2 { } /// Creates a [`Rot2`] from a counterclockwise angle in degrees. + /// + /// # Note + /// + /// The input rotation will always be clamped to the range `(-180°, 180°]` by design. + /// + /// # Example + /// + /// ``` + /// # use bevy_math::Rot2; + /// # use approx::assert_relative_eq; + /// + /// let rot1 = Rot2::degrees(270.0); + /// let rot2 = Rot2::degrees(-90.0); + /// assert_relative_eq!(rot1, rot2); + /// + /// let rot3 = Rot2::degrees(180.0); + /// assert_relative_eq!(rot1 * rot1, rot3); + /// + /// ``` #[inline] pub fn degrees(degrees: f32) -> Self { Self::radians(degrees.to_radians()) } + /// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees. + /// + /// # Note + /// + /// The input rotation will always be clamped to the range `(-50%, 50%]` by design. + /// + /// # Example + /// + /// ``` + /// # use bevy_math::Rot2; + /// # use approx::assert_relative_eq; + /// + /// let rot1 = Rot2::turn_fraction(0.75); + /// let rot2 = Rot2::turn_fraction(-0.25); + /// assert_relative_eq!(rot1, rot2); + /// + /// let rot3 = Rot2::turn_fraction(0.5); + /// assert_relative_eq!(rot1 * rot1, rot3); + /// + /// ``` + #[inline] + pub fn turn_fraction(fraction: f32) -> Self { + Self::radians(TAU * fraction) + } + /// Creates a [`Rot2`] from the sine and cosine of an angle in radians. /// /// The rotation is only valid if `sin * sin + cos * cos == 1.0`. @@ -141,6 +207,12 @@ impl Rot2 { self.as_radians().to_degrees() } + /// Returns the rotation as a fraction of a full 360 degree turn. + #[inline] + pub fn as_turn_fraction(self) -> f32 { + self.as_radians() / TAU + } + /// Returns the sine and cosine of the rotation angle in radians. #[inline] pub const fn sin_cos(self) -> (f32, f32) { @@ -437,25 +509,31 @@ impl approx::UlpsEq for Rot2 { #[cfg(test)] mod tests { + use std::f32::consts::FRAC_PI_2; + use approx::assert_relative_eq; use crate::{Dir2, Rot2, Vec2}; #[test] fn creation() { - let rotation1 = Rot2::radians(std::f32::consts::FRAC_PI_2); + let rotation1 = Rot2::radians(FRAC_PI_2); let rotation2 = Rot2::degrees(90.0); let rotation3 = Rot2::from_sin_cos(1.0, 0.0); + let rotation4 = Rot2::turn_fraction(0.25); // All three rotations should be equal assert_relative_eq!(rotation1.sin, rotation2.sin); assert_relative_eq!(rotation1.cos, rotation2.cos); assert_relative_eq!(rotation1.sin, rotation3.sin); assert_relative_eq!(rotation1.cos, rotation3.cos); + assert_relative_eq!(rotation1.sin, rotation4.sin); + assert_relative_eq!(rotation1.cos, rotation4.cos); // The rotation should be 90 degrees - assert_relative_eq!(rotation1.as_radians(), std::f32::consts::FRAC_PI_2); + assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2); assert_relative_eq!(rotation1.as_degrees(), 90.0); + assert_relative_eq!(rotation1.as_turn_fraction(), 0.25); } #[test] @@ -466,12 +544,21 @@ mod tests { assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X); } + #[test] + fn rotation_range() { + // the rotation range is `(-180, 180]` and the constructors + // normalize the rotations to that range + assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2)); + assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0)); + assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25)); + } + #[test] fn add() { let rotation1 = Rot2::degrees(90.0); let rotation2 = Rot2::degrees(180.0); - // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the ]-180, 180] range + // 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range assert_eq!((rotation1 * rotation2).as_degrees(), -90.0); } diff --git a/crates/bevy_math/src/sampling/shape_sampling.rs b/crates/bevy_math/src/sampling/shape_sampling.rs index 10f9e17f60..10d99f8a8b 100644 --- a/crates/bevy_math/src/sampling/shape_sampling.rs +++ b/crates/bevy_math/src/sampling/shape_sampling.rs @@ -450,7 +450,7 @@ impl ShapeSample for Capsule2d { if capsule_area > 0.0 { // Check if the random point should be inside the rectangle if rng.gen_bool((rectangle_area / capsule_area) as f64) { - let rectangle = Rectangle::new(self.radius, self.half_length * 2.0); + let rectangle = Rectangle::new(self.radius * 2.0, self.half_length * 2.0); rectangle.sample_interior(rng) } else { let circle = Circle::new(self.radius); diff --git a/crates/bevy_pbr/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs index e1147a46e4..52c7c64384 100644 --- a/crates/bevy_pbr/src/bundle.rs +++ b/crates/bevy_pbr/src/bundle.rs @@ -6,6 +6,7 @@ use bevy_asset::Handle; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::{Entity, EntityHashMap}; use bevy_ecs::{bundle::Bundle, component::Component, reflect::ReflectComponent}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ mesh::Mesh, @@ -50,14 +51,14 @@ impl Default for MaterialMeshBundle { /// This component contains all mesh entities visible from the current light view. /// The collection is updated automatically by [`crate::SimulationLightSystems`]. #[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] -#[reflect(Component)] +#[reflect(Component, Debug, Default)] pub struct VisibleMeshEntities { #[reflect(ignore)] pub entities: Vec, } #[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug, Default)] pub struct CubemapVisibleEntities { #[reflect(ignore)] data: [VisibleMeshEntities; 6], diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index f2a6cdc3ae..8e358a29b9 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ entity::Entity, system::{Commands, Local, Query, Res, ResMut}, }; -use bevy_math::{Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _}; +use bevy_math::{ops, Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _}; use bevy_render::{ camera::Camera, primitives::{Aabb, Frustum, HalfSpace, Sphere}, @@ -484,7 +484,7 @@ pub(crate) fn assign_objects_to_clusters( radius: clusterable_object_sphere.radius * view_from_world_scale_max, }; let spot_light_dir_sin_cos = clusterable_object.spot_light_angle.map(|angle| { - let (angle_sin, angle_cos) = angle.sin_cos(); + let (angle_sin, angle_cos) = ops::sin_cos(angle); ( (view_from_world * clusterable_object.transform.back().extend(0.0)) .truncate() @@ -723,14 +723,18 @@ fn compute_aabb_for_cluster( let cluster_near = if ijk.z == 0.0 { 0.0 } else { - -z_near * z_far_over_z_near.powf((ijk.z - 1.0) / (cluster_dimensions.z - 1) as f32) + -z_near + * ops::powf( + z_far_over_z_near, + (ijk.z - 1.0) / (cluster_dimensions.z - 1) as f32, + ) }; // NOTE: This could be simplified to: // cluster_far = cluster_near * z_far_over_z_near; let cluster_far = if cluster_dimensions.z == 1 { -z_far } else { - -z_near * z_far_over_z_near.powf(ijk.z / (cluster_dimensions.z - 1) as f32) + -z_near * ops::powf(z_far_over_z_near, ijk.z / (cluster_dimensions.z - 1) as f32) }; // Calculate the four intersection points of the min and max points with the cluster near and far planes @@ -762,7 +766,7 @@ fn z_slice_to_view_z( if z_slice == 0 { 0.0 } else { - -near * (far / near).powf((z_slice - 1) as f32 / (z_slices - 1) as f32) + -near * ops::powf(far / near, (z_slice - 1) as f32 / (z_slices - 1) as f32) } } @@ -900,7 +904,7 @@ fn view_z_to_z_slice( ((view_z - cluster_factors.x) * cluster_factors.y).floor() as u32 } else { // NOTE: had to use -view_z to make it positive else log(negative) is nan - ((-view_z).ln() * cluster_factors.x - cluster_factors.y + 1.0) as u32 + (ops::ln(-view_z) * cluster_factors.x - cluster_factors.y + 1.0) as u32 }; // NOTE: We use min as we may limit the far z plane used for clustering to be closer than // the furthest thing being drawn. This means that we need to limit to the maximum cluster. diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 7da6c5da02..add1b38850 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -80,7 +80,7 @@ pub struct ClusterZConfig { /// Configuration of the clustering strategy for clustered forward rendering #[derive(Debug, Copy, Clone, Component, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug, Default)] pub enum ClusterConfig { /// Disable cluster calculations for this view None, @@ -152,6 +152,10 @@ pub struct GpuClusterableObject { pub(crate) shadow_depth_bias: f32, pub(crate) shadow_normal_bias: f32, pub(crate) spot_light_tan_angle: f32, + pub(crate) soft_shadow_size: f32, + pub(crate) shadow_map_near_z: f32, + pub(crate) pad_a: f32, + pub(crate) pad_b: f32, } pub enum GpuClusterableObjects { @@ -257,8 +261,9 @@ impl ClusterConfig { ClusterConfig::FixedZ { total, z_slices, .. } => { - let aspect_ratio: f32 = - AspectRatio::from_pixels(screen_size.x, screen_size.y).into(); + let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y) + .expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values") + .ratio(); let mut z_slices = *z_slices; if *total < z_slices { warn!("ClusterConfig has more z-slices than total clusters!"); diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index eb4ba66cf0..10d8a57962 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -1,6 +1,6 @@ use crate::{ graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight, - MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusionSettings, + MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, }; @@ -259,11 +259,11 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), - 21, + 22, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), - 22, + 23, )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); @@ -434,7 +434,7 @@ pub fn prepare_deferred_lighting_pipelines( Option<&DebandDither>, Option<&ShadowFilteringMethod>, ( - Has, + Has, Has, ), ( diff --git a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl index f317f15b5a..b892181377 100644 --- a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl +++ b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl @@ -76,10 +76,10 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4, gbuffer: vec4) -> let emissive = rgb9e5::rgb9e5_to_vec3_(gbuffer.g); if ((pbr.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) { pbr.material.base_color = vec4(emissive, 1.0); - pbr.material.emissive = vec4(vec3(0.0), 1.0); + pbr.material.emissive = vec4(vec3(0.0), 0.0); } else { pbr.material.base_color = vec4(pow(base_rough.rgb, vec3(2.2)), 1.0); - pbr.material.emissive = vec4(emissive, 1.0); + pbr.material.emissive = vec4(emissive, 0.0); } #ifdef WEBGL2 // More crunched for webgl so we can also fit depth. let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b); diff --git a/crates/bevy_pbr/src/fog.rs b/crates/bevy_pbr/src/fog.rs index 894ced95a4..5f4738d619 100644 --- a/crates/bevy_pbr/src/fog.rs +++ b/crates/bevy_pbr/src/fog.rs @@ -1,6 +1,6 @@ use bevy_color::{Color, ColorToComponents, LinearRgba}; use bevy_ecs::prelude::*; -use bevy_math::Vec3; +use bevy_math::{ops, Vec3}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; @@ -34,7 +34,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; /// # ..Default::default() /// }, /// // Add fog to the same entity -/// FogSettings { +/// DistanceFog { /// color: Color::WHITE, /// falloff: FogFalloff::Exponential { density: 1e-3 }, /// ..Default::default() @@ -50,8 +50,8 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; /// [`StandardMaterial`](crate::StandardMaterial) instances via the `fog_enabled` flag. #[derive(Debug, Clone, Component, Reflect, ExtractComponent)] #[extract_component_filter(With)] -#[reflect(Component, Default)] -pub struct FogSettings { +#[reflect(Component, Default, Debug)] +pub struct DistanceFog { /// The color of the fog effect. /// /// **Tip:** The alpha channel of the color can be used to “modulate” the fog effect without @@ -73,6 +73,9 @@ pub struct FogSettings { pub falloff: FogFalloff, } +#[deprecated(since = "0.15.0", note = "Renamed to `DistanceFog`")] +pub type FogSettings = DistanceFog; + /// Allows switching between different fog falloff modes, and configuring their parameters. /// /// ## Convenience Methods @@ -149,7 +152,7 @@ pub enum FogFalloff { /// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values /// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of /// density; - /// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control. + /// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control. /// /// ## Formula /// @@ -197,14 +200,14 @@ pub enum FogFalloff { /// /// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff /// with the proper density for a desired visibility distance in world units; - /// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control. + /// - Combine the `density` parameter with the [`DistanceFog`] `color`'s alpha channel for easier artistic control. /// /// ## Formula /// /// The fog intensity for a given point in the scene is determined by the following formula: /// /// ```text - /// let fog_intensity = 1.0 - 1.0 / (distance * density).powi(2).exp(); + /// let fog_intensity = 1.0 - 1.0 / (distance * density).squared().exp(); /// ``` /// /// @@ -244,7 +247,7 @@ pub enum FogFalloff { /// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods /// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and /// extinction and inscattering colors; - /// - Combine the atmospheric fog parameters with the [`FogSettings`] `color`'s alpha channel for easier artistic control. + /// - Combine the atmospheric fog parameters with the [`DistanceFog`] `color`'s alpha channel for easier artistic control. /// /// ## Formula /// @@ -416,15 +419,15 @@ impl FogFalloff { // Values are subtracted from 1.0 here to preserve the intuitive/artistic meaning of // colors, since they're later subtracted. (e.g. by giving a blue extinction color, you // get blue and _not_ yellow results) - (1.0 - r_e).powf(E), - (1.0 - g_e).powf(E), - (1.0 - b_e).powf(E), + ops::powf(1.0 - r_e, E), + ops::powf(1.0 - g_e, E), + ops::powf(1.0 - b_e, E), ) * FogFalloff::koschmieder(visibility, contrast_threshold) - * a_e.powf(E), + * ops::powf(a_e, E), - inscattering: Vec3::new(r_i.powf(E), g_i.powf(E), b_i.powf(E)) + inscattering: Vec3::new(ops::powf(r_i, E), ops::powf(g_i, E), ops::powf(b_i, E)) * FogFalloff::koschmieder(visibility, contrast_threshold) - * a_i.powf(E), + * ops::powf(a_i, E), } } @@ -459,13 +462,13 @@ impl FogFalloff { /// - /// - pub fn koschmieder(v: f32, c_t: f32) -> f32 { - -c_t.ln() / v + -ops::ln(c_t) / v } } -impl Default for FogSettings { +impl Default for DistanceFog { fn default() -> Self { - FogSettings { + DistanceFog { color: Color::WHITE, falloff: FogFalloff::Linear { start: 0.0, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index ba2848b8a8..ed57085363 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -57,8 +57,10 @@ pub use prepass::*; pub use render::*; pub use ssao::*; pub use ssr::*; +#[allow(deprecated)] pub use volumetric_fog::{ - FogVolume, FogVolumeBundle, VolumetricFogPlugin, VolumetricFogSettings, VolumetricLight, + FogVolume, FogVolumeBundle, VolumetricFog, VolumetricFogPlugin, VolumetricFogSettings, + VolumetricLight, }; /// The PBR prelude. @@ -71,7 +73,7 @@ pub mod prelude { DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle, SpotLightBundle, }, - fog::{FogFalloff, FogSettings}, + fog::{DistanceFog, FogFalloff}, light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight}, light_probe::{ environment_map::{EnvironmentMapLight, ReflectionProbeBundle}, @@ -303,7 +305,7 @@ impl Plugin for PbrPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() + .register_type::() .register_type::() .init_resource::() .init_resource::() diff --git a/crates/bevy_pbr/src/light/ambient_light.rs b/crates/bevy_pbr/src/light/ambient_light.rs index 9bff67d5f8..9c42eea516 100644 --- a/crates/bevy_pbr/src/light/ambient_light.rs +++ b/crates/bevy_pbr/src/light/ambient_light.rs @@ -16,7 +16,7 @@ use super::*; /// } /// ``` #[derive(Resource, Clone, Debug, ExtractResource, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Debug, Default)] pub struct AmbientLight { pub color: Color, /// A direct scale factor multiplied with `color` before being passed to the shader. diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_pbr/src/light/directional_light.rs index 9c74971ca1..89bc3d2846 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_pbr/src/light/directional_light.rs @@ -48,9 +48,13 @@ use super::*; /// .insert_resource(DirectionalLightShadowMap { size: 2048 }); /// ``` #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct DirectionalLight { + /// The color of the light. + /// + /// By default, this is white. pub color: Color, + /// Illuminance in lux (lumens per square meter), representing the amount of /// light projected onto surfaces by this light source. Lux is used here /// instead of lumens because a directional light illuminates all surfaces @@ -58,10 +62,45 @@ pub struct DirectionalLight { /// can only be specified for light sources which emit light from a specific /// area. pub illuminance: f32, + + /// Whether this light casts shadows. + /// + /// Note that shadows are rather expensive and become more so with every + /// light that casts them. In general, it's best to aggressively limit the + /// number of lights with shadows enabled to one or two at most. pub shadows_enabled: bool, + + /// Whether soft shadows are enabled, and if so, the size of the light. + /// + /// Soft shadows, also known as *percentage-closer soft shadows* or PCSS, + /// cause shadows to become blurrier (i.e. their penumbra increases in + /// radius) as they extend away from objects. The blurriness of the shadow + /// depends on the size of the light; larger lights result in larger + /// penumbras and therefore blurrier shadows. + /// + /// Currently, soft shadows are rather noisy if not using the temporal mode. + /// If you enable soft shadows, consider choosing + /// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing + /// (TAA) to smooth the noise out over time. + /// + /// Note that soft shadows are significantly more expensive to render than + /// hard shadows. + pub soft_shadow_size: Option, + + /// A value that adjusts the tradeoff between self-shadowing artifacts and + /// proximity of shadows to their casters. + /// + /// This value frequently must be tuned to the specific scene; this is + /// normal and a well-known part of the shadow mapping workflow. If set too + /// low, unsightly shadow patterns appear on objects not in shadow as + /// objects incorrectly cast shadows on themselves, known as *shadow acne*. + /// If set too high, shadows detach from the objects casting them and seem + /// to "fly" off the objects, known as *Peter Panning*. pub shadow_depth_bias: f32, - /// A bias applied along the direction of the fragment's surface normal. It is scaled to the - /// shadow map's texel size so that it is automatically adjusted to the orthographic projection. + + /// A bias applied along the direction of the fragment's surface normal. It + /// is scaled to the shadow map's texel size so that it is automatically + /// adjusted to the orthographic projection. pub shadow_normal_bias: f32, } @@ -71,6 +110,7 @@ impl Default for DirectionalLight { color: Color::WHITE, illuminance: light_consts::lux::AMBIENT_DAYLIGHT, shadows_enabled: false, + soft_shadow_size: None, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, } diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index d0db103c93..28b4630411 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -2,7 +2,7 @@ use std::ops::DerefMut; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; -use bevy_math::{Mat4, Vec3A, Vec4}; +use bevy_math::{ops, Mat4, Vec3A, Vec4}; use bevy_reflect::prelude::*; use bevy_render::{ camera::{Camera, CameraProjection}, @@ -11,7 +11,8 @@ use bevy_render::{ mesh::Mesh, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, view::{ - InheritedVisibility, RenderLayers, ViewVisibility, VisibilityRange, VisibleEntityRanges, + InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, + VisibleEntityRanges, }, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -87,7 +88,7 @@ pub mod light_consts { } #[derive(Resource, Clone, Debug, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Debug, Default)] pub struct PointLightShadowMap { pub size: usize, } @@ -104,7 +105,7 @@ pub type WithLight = Or<(With, With, With, @@ -153,9 +154,12 @@ fn calculate_cascade_bounds( if num_cascades == 1 { return vec![shadow_maximum_distance]; } - let base = (shadow_maximum_distance / nearest_bound).powf(1.0 / (num_cascades - 1) as f32); + let base = ops::powf( + shadow_maximum_distance / nearest_bound, + 1.0 / (num_cascades - 1) as f32, + ); (0..num_cascades) - .map(|i| nearest_bound * base.powf(i as f32)) + .map(|i| nearest_bound * ops::powf(base, i as f32)) .collect() } @@ -270,7 +274,7 @@ impl From for CascadeShadowConfig { } #[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug, Default)] pub struct Cascades { /// Map from a view to the configuration of each of its [`Cascade`]s. pub(crate) cascades: EntityHashMap>, @@ -438,7 +442,7 @@ fn calculate_cascade( } /// Add this component to make a [`Mesh`] not cast shadows. #[derive(Debug, Component, Reflect, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct NotShadowCaster; /// Add this component to make a [`Mesh`] not receive shadows. /// @@ -446,7 +450,7 @@ pub struct NotShadowCaster; /// cause both “regular” shadows as well as diffusely transmitted shadows to be disabled, /// even when [`TransmittedShadowReceiver`] is being used. #[derive(Debug, Component, Reflect, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct NotShadowReceiver; /// Add this component to make a [`Mesh`] using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0` /// receive shadows on its diffuse transmission lobe. (i.e. its “backside”) @@ -456,7 +460,7 @@ pub struct NotShadowReceiver; /// /// **Note:** Using [`NotShadowReceiver`] overrides this component. #[derive(Debug, Component, Reflect, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct TransmittedShadowReceiver; /// Add this component to a [`Camera3d`](bevy_core_pipeline::core_3d::Camera3d) @@ -465,7 +469,7 @@ pub struct TransmittedShadowReceiver; /// The different modes use different approaches to /// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing). #[derive(Debug, Component, ExtractComponent, Reflect, Clone, Copy, PartialEq, Eq, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub enum ShadowFilteringMethod { /// Hardware 2x2. /// @@ -485,7 +489,7 @@ pub enum ShadowFilteringMethod { /// A randomized filter that varies over time, good when TAA is in use. /// /// Good quality when used with - /// [`TemporalAntiAliasSettings`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasSettings) + /// [`TemporalAntiAliasing`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasing) /// and good performance. /// /// For directional and spot lights, this uses a [method by Jorge Jimenez for @@ -576,8 +580,6 @@ pub fn update_point_light_frusta( Or<(Changed, Changed)>, >, ) { - let clip_from_view = - Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z); let view_rotations = CUBE_MAP_FACES .iter() .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up)) @@ -593,6 +595,12 @@ pub fn update_point_light_frusta( continue; } + let clip_from_view = Mat4::perspective_infinite_reverse_rh( + std::f32::consts::FRAC_PI_2, + 1.0, + point_light.shadow_map_near_z, + ); + // ignore scale because we don't want to effectively scale light radius and range // by applying those as a view transform to shadow map rendering of objects // and ignore rotation because we want the shadow map projections to align with the axes @@ -635,7 +643,8 @@ pub fn update_spot_light_frusta( let view_backward = transform.back(); let spot_world_from_view = spot_light_world_from_view(transform); - let spot_clip_from_view = spot_light_clip_from_view(spot_light.outer_angle); + let spot_clip_from_view = + spot_light_clip_from_view(spot_light.outer_angle, spot_light.shadow_map_near_z); let clip_from_world = spot_clip_from_view * spot_world_from_view.inverse(); *frustum = Frustum::from_clip_from_world_custom_far( @@ -683,6 +692,7 @@ pub fn check_dir_light_mesh_visibility( Option<&Aabb>, Option<&GlobalTransform>, Has, + Has, ), ( Without, @@ -742,6 +752,7 @@ pub fn check_dir_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -768,7 +779,9 @@ pub fn check_dir_light_mesh_visibility( .zip(view_visible_entities_local_queue.iter_mut()) { // Disable near-plane culling, as a shadow caster could lie before the near plane. - if !frustum.intersects_obb(aabb, &transform.affine(), false, true) { + if !has_no_frustum_culling + && !frustum.intersects_obb(aabb, &transform.affine(), false, true) + { continue; } visible = true; @@ -812,7 +825,7 @@ pub fn check_dir_light_mesh_visibility( // Defer marking view visibility so this system can run in parallel with check_point_light_mesh_visibility // TODO: use resource to avoid unnecessary memory alloc let mut defer_queue = std::mem::take(defer_visible_entities_queue.deref_mut()); - commands.add(move |world: &mut World| { + 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()); @@ -848,6 +861,7 @@ pub fn check_point_light_mesh_visibility( Option<&Aabb>, Option<&GlobalTransform>, Has, + Has, ), ( Without, @@ -897,6 +911,7 @@ pub fn check_point_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -917,7 +932,9 @@ pub fn check_point_light_mesh_visibility( if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { let model_to_world = transform.affine(); // Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light - if !light_sphere.intersects_obb(aabb, &model_to_world) { + if !has_no_frustum_culling + && !light_sphere.intersects_obb(aabb, &model_to_world) + { return; } @@ -925,7 +942,9 @@ pub fn check_point_light_mesh_visibility( .iter() .zip(cubemap_visible_entities_local_queue.iter_mut()) { - if frustum.intersects_obb(aabb, &model_to_world, true, true) { + if has_no_frustum_culling + || frustum.intersects_obb(aabb, &model_to_world, true, true) + { view_visibility.set(); visible_entities.push(entity); } @@ -980,6 +999,7 @@ pub fn check_point_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -1001,11 +1021,15 @@ pub fn check_point_light_mesh_visibility( if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { let model_to_world = transform.affine(); // Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light - if !light_sphere.intersects_obb(aabb, &model_to_world) { + if !has_no_frustum_culling + && !light_sphere.intersects_obb(aabb, &model_to_world) + { return; } - if frustum.intersects_obb(aabb, &model_to_world, true, true) { + if has_no_frustum_culling + || frustum.intersects_obb(aabb, &model_to_world, true, true) + { view_visibility.set(); spot_visible_entities_local_queue.push(entity); } diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_pbr/src/light/point_light.rs index 9ca85cd4db..b4db7ce4f4 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_pbr/src/light/point_light.rs @@ -18,33 +18,67 @@ use super::*; /// /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting) #[derive(Component, Debug, Clone, Copy, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct PointLight { /// The color of this light source. pub color: Color, + /// Luminous power in lumens, representing the amount of light emitted by this source in all directions. pub intensity: f32, + /// Cut-off for the light's area-of-effect. Fragments outside this range will not be affected by /// this light at all, so it's important to tune this together with `intensity` to prevent hard /// lighting cut-offs. pub range: f32, - /// Simulates a light source coming from a spherical volume with the given radius. Only affects - /// the size of specular highlights created by this light. Because of this, large values may not - /// produce the intended result -- for example, light radius does not affect shadow softness or - /// diffuse lighting. + + /// Simulates a light source coming from a spherical volume with the given + /// radius. + /// + /// This affects the size of specular highlights created by this light, as + /// well as the soft shadow penumbra size. Because of this, large values may + /// not produce the intended result -- for example, light radius does not + /// affect shadow softness or diffuse lighting. pub radius: f32, + /// Whether this light casts shadows. pub shadows_enabled: bool, + + /// Whether soft shadows are enabled. + /// + /// Soft shadows, also known as *percentage-closer soft shadows* or PCSS, + /// cause shadows to become blurrier (i.e. their penumbra increases in + /// radius) as they extend away from objects. The blurriness of the shadow + /// depends on the [`PointLight::radius`] of the light; larger lights result + /// in larger penumbras and therefore blurrier shadows. + /// + /// Currently, soft shadows are rather noisy if not using the temporal mode. + /// If you enable soft shadows, consider choosing + /// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing + /// (TAA) to smooth the noise out over time. + /// + /// Note that soft shadows are significantly more expensive to render than + /// hard shadows. + pub soft_shadows_enabled: bool, + /// A bias used when sampling shadow maps to avoid "shadow-acne", or false shadow occlusions /// that happen as a result of shadow-map fragments not mapping 1:1 to screen-space fragments. /// Too high of a depth bias can lead to shadows detaching from their casters, or /// "peter-panning". This bias can be tuned together with `shadow_normal_bias` to correct shadow /// artifacts for a given scene. pub shadow_depth_bias: f32, + /// A bias applied along the direction of the fragment's surface normal. It is scaled to the /// shadow map's texel size so that it can be small close to the camera and gets larger further /// away. pub shadow_normal_bias: f32, + + /// The distance from the light to near Z plane in the shadow map. + /// + /// Objects closer than this distance to the light won't cast shadows. + /// Setting this higher increases the shadow map's precision. + /// + /// This only has an effect if shadows are enabled. + pub shadow_map_near_z: f32, } impl Default for PointLight { @@ -58,8 +92,10 @@ impl Default for PointLight { range: 20.0, radius: 0.0, shadows_enabled: false, + soft_shadows_enabled: false, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, + shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z, } } } @@ -67,4 +103,5 @@ impl Default for PointLight { impl PointLight { pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.08; pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; + pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1; } diff --git a/crates/bevy_pbr/src/light/spot_light.rs b/crates/bevy_pbr/src/light/spot_light.rs index ab34196ff0..f2e595fbf8 100644 --- a/crates/bevy_pbr/src/light/spot_light.rs +++ b/crates/bevy_pbr/src/light/spot_light.rs @@ -5,25 +5,87 @@ use super::*; /// shines light only in a given direction. The direction is taken from /// the transform, and can be specified with [`Transform::looking_at`](Transform::looking_at). #[derive(Component, Debug, Clone, Copy, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct SpotLight { + /// The color of the light. + /// + /// By default, this is white. pub color: Color, + /// Luminous power in lumens, representing the amount of light emitted by this source in all directions. pub intensity: f32, + + /// Range in meters that this light illuminates. + /// + /// Note that this value affects resolution of the shadow maps; generally, the + /// higher you set it, the lower-resolution your shadow maps will be. + /// Consequently, you should set this value to be only the size that you need. pub range: f32, + + /// Simulates a light source coming from a spherical volume with the given + /// radius. + /// + /// This affects the size of specular highlights created by this light, as + /// well as the soft shadow penumbra size. Because of this, large values may + /// not produce the intended result -- for example, light radius does not + /// affect shadow softness or diffuse lighting. pub radius: f32, + + /// Whether this light casts shadows. + /// + /// Note that shadows are rather expensive and become more so with every + /// light that casts them. In general, it's best to aggressively limit the + /// number of lights with shadows enabled to one or two at most. pub shadows_enabled: bool, + + /// Whether soft shadows are enabled. + /// + /// Soft shadows, also known as *percentage-closer soft shadows* or PCSS, + /// cause shadows to become blurrier (i.e. their penumbra increases in + /// radius) as they extend away from objects. The blurriness of the shadow + /// depends on the [`SpotLight::radius`] of the light; larger lights result in larger + /// penumbras and therefore blurrier shadows. + /// + /// Currently, soft shadows are rather noisy if not using the temporal mode. + /// If you enable soft shadows, consider choosing + /// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing + /// (TAA) to smooth the noise out over time. + /// + /// Note that soft shadows are significantly more expensive to render than + /// hard shadows. + pub soft_shadows_enabled: bool, + + /// A value that adjusts the tradeoff between self-shadowing artifacts and + /// proximity of shadows to their casters. + /// + /// This value frequently must be tuned to the specific scene; this is + /// normal and a well-known part of the shadow mapping workflow. If set too + /// low, unsightly shadow patterns appear on objects not in shadow as + /// objects incorrectly cast shadows on themselves, known as *shadow acne*. + /// If set too high, shadows detach from the objects casting them and seem + /// to "fly" off the objects, known as *Peter Panning*. pub shadow_depth_bias: f32, + /// A bias applied along the direction of the fragment's surface normal. It is scaled to the /// shadow map's texel size so that it can be small close to the camera and gets larger further /// away. pub shadow_normal_bias: f32, + + /// The distance from the light to the near Z plane in the shadow map. + /// + /// Objects closer than this distance to the light won't cast shadows. + /// Setting this higher increases the shadow map's precision. + /// + /// This only has an effect if shadows are enabled. + pub shadow_map_near_z: f32, + /// Angle defining the distance from the spot light direction to the outer limit /// of the light's cone of effect. /// `outer_angle` should be < `PI / 2.0`. /// `PI / 2.0` defines a hemispherical spot light, but shadows become very blocky as the angle /// approaches this limit. pub outer_angle: f32, + /// Angle defining the distance from the spot light direction to the inner limit /// of the light's cone of effect. /// Light is attenuated from `inner_angle` to `outer_angle` to give a smooth falloff. @@ -34,6 +96,7 @@ pub struct SpotLight { impl SpotLight { pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02; pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8; + pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1; } impl Default for SpotLight { @@ -48,8 +111,10 @@ impl Default for SpotLight { range: 20.0, radius: 0.0, shadows_enabled: false, + soft_shadows_enabled: false, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, + shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z, inner_angle: 0.0, outer_angle: std::f32::consts::FRAC_PI_4, } diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 1b1604df4d..a9be284fba 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -48,9 +48,11 @@ use bevy_asset::{AssetId, Handle}; use bevy_ecs::{ - bundle::Bundle, component::Component, query::QueryItem, system::lifetimeless::Read, + bundle::Bundle, component::Component, query::QueryItem, reflect::ReflectComponent, + system::lifetimeless::Read, }; use bevy_math::Quat; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ extract_instances::ExtractInstance, @@ -84,6 +86,7 @@ pub const ENVIRONMENT_MAP_SHADER_HANDLE: Handle = /// /// See [`crate::environment_map`] for detailed information. #[derive(Clone, Component, Reflect)] +#[reflect(Component, Default)] pub struct EnvironmentMapLight { /// The blurry image that represents diffuse radiance surrounding a region. pub diffuse_map: Handle, diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 58110e98af..34f9f902a6 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -132,7 +132,7 @@ //! //! [Why ambient cubes?]: #why-ambient-cubes -use bevy_ecs::component::Component; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_render::{ render_asset::RenderAssets, render_resource::{ @@ -145,6 +145,7 @@ use bevy_render::{ use std::{num::NonZero, ops::Deref}; use bevy_asset::{AssetId, Handle}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use crate::{ @@ -166,6 +167,7 @@ pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "w /// /// See [`crate::irradiance_volume`] for detailed information. #[derive(Clone, Default, Reflect, Component, Debug)] +#[reflect(Component, Default, Debug)] pub struct IrradianceVolume { /// The 3D texture that represents the ambient cubes, encoded in the format /// described in [`crate::irradiance_volume`]. diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index dbcdd680d9..7d84a28b19 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -103,7 +103,7 @@ pub struct LightProbePlugin; /// specific technique but rather to a class of techniques. Developers familiar /// with other engines should be aware of this terminology difference. #[derive(Component, Debug, Clone, Copy, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct LightProbe; /// A GPU type that stores information about a light probe. diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 9da61b2ce9..8bf6ae254d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -20,6 +20,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ camera::TemporalJitter, @@ -553,7 +554,7 @@ pub fn queue_material_meshes( Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, - Has, + Has, ( Has, Has, @@ -825,6 +826,7 @@ pub fn queue_material_meshes( /// Default render method used for opaque materials. #[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)] +#[reflect(Resource, Default, Debug)] pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod); impl DefaultOpaqueRendererMethod { diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index 1a5c3e2d56..6a0f093d81 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -45,7 +45,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, - Has, + Has, ( Has, Has, diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs index 03855ec039..7c25be7529 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs @@ -13,7 +13,7 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, }; use bevy_utils::HashMap; -use std::{mem::size_of, ops::Range, sync::Arc}; +use std::{ops::Range, sync::Arc}; /// Manages uploading [`MeshletMesh`] asset data to the GPU. #[derive(Resource)] diff --git a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs index da341c285e..175fbe7514 100644 --- a/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs +++ b/crates/bevy_pbr/src/meshlet/persistent_buffer_impls.rs @@ -2,7 +2,7 @@ use super::{ asset::{Meshlet, MeshletBoundingSpheres}, persistent_buffer::PersistentGpuBufferable, }; -use std::{mem::size_of, sync::Arc}; +use std::sync::Arc; const MESHLET_VERTEX_SIZE_IN_BYTES: u32 = 48; diff --git a/crates/bevy_pbr/src/meshlet/resource_manager.rs b/crates/bevy_pbr/src/meshlet/resource_manager.rs index 297987ef84..4df1fb8fd3 100644 --- a/crates/bevy_pbr/src/meshlet/resource_manager.rs +++ b/crates/bevy_pbr/src/meshlet/resource_manager.rs @@ -21,7 +21,6 @@ use binding_types::*; use encase::internal::WriteInto; use std::{ array, iter, - mem::size_of, sync::{atomic::AtomicBool, Arc}, }; 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 3f2b8883e7..e05956fc80 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -9,6 +9,7 @@ use bevy_ecs::{ query::QueryState, world::{FromWorld, World}, }; +use bevy_math::ops; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext}, @@ -101,10 +102,8 @@ impl Node for MeshletVisibilityBufferRasterPassNode { .first_node .fetch_and(false, Ordering::SeqCst); - let thread_per_cluster_workgroups = - (meshlet_view_resources.scene_cluster_count.div_ceil(128) as f32) - .cbrt() - .ceil() as u32; + let div_ceil = meshlet_view_resources.scene_cluster_count.div_ceil(128); + let thread_per_cluster_workgroups = ops::cbrt(div_ceil as f32).ceil() as u32; render_context .command_encoder() diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 916a1f098c..9e4a1c920b 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -1,6 +1,6 @@ use bevy_asset::Asset; use bevy_color::{Alpha, ColorToComponents}; -use bevy_math::{vec2, Affine2, Affine3, Mat2, Mat3, Vec2, Vec3, Vec4}; +use bevy_math::{Affine2, Affine3, Mat2, Mat3, Vec2, Vec3, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ mesh::MeshVertexBufferLayoutRef, render_asset::RenderAssets, render_resource::*, @@ -1068,10 +1068,7 @@ impl AsBindGroupShaderType for StandardMaterial { emissive[3] = self.emissive_exposure_weight; // Doing this up front saves having to do this repeatedly in the fragment shader. - let anisotropy_rotation = vec2( - self.anisotropy_rotation.cos(), - self.anisotropy_rotation.sin(), - ); + let anisotropy_rotation = Vec2::from_angle(self.anisotropy_rotation); StandardMaterialUniform { base_color: LinearRgba::from(self.base_color).to_vec4(), diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 1421ddcd6d..02bd6ac736 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -11,7 +11,7 @@ use bevy_render::{ Render, RenderApp, RenderSet, }; -use crate::{FogFalloff, FogSettings}; +use crate::{DistanceFog, FogFalloff}; /// The GPU-side representation of the fog configuration that's sent as a uniform to the shader #[derive(Copy, Clone, ShaderType, Default, Debug)] @@ -51,7 +51,7 @@ pub fn prepare_fog( render_device: Res, render_queue: Res, mut fog_meta: ResMut, - views: Query<(Entity, Option<&FogSettings>), With>, + views: Query<(Entity, Option<&DistanceFog>), With>, ) { let views_iter = views.iter(); let view_count = views_iter.len(); @@ -136,8 +136,8 @@ impl Plugin for FogPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, FOG_SHADER_HANDLE, "fog.wgsl", Shader::from_wgsl); - app.register_type::(); - app.add_plugins(ExtractComponentPlugin::::default()); + app.register_type::(); + app.add_plugins(ExtractComponentPlugin::::default()); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6bf1013295..86fd2151ce 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -4,7 +4,7 @@ use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; use bevy_ecs::entity::EntityHashSet; use bevy_ecs::prelude::*; use bevy_ecs::{entity::EntityHashMap, system::lifetimeless::Read}; -use bevy_math::{Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; +use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_render::{ diagnostic::RecordDiagnostics, mesh::RenderMesh, @@ -19,6 +19,7 @@ use bevy_render::{ Extract, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; +use bevy_utils::prelude::default; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; use bevy_utils::tracing::{error, warn}; @@ -35,8 +36,10 @@ pub struct ExtractedPointLight { pub radius: f32, pub transform: GlobalTransform, pub shadows_enabled: bool, + pub soft_shadows_enabled: bool, pub shadow_depth_bias: f32, pub shadow_normal_bias: f32, + pub shadow_map_near_z: f32, pub spot_light_angles: Option<(f32, f32)>, } @@ -47,6 +50,7 @@ pub struct ExtractedDirectionalLight { pub transform: GlobalTransform, pub shadows_enabled: bool, pub volumetric: bool, + pub soft_shadow_size: Option, pub shadow_depth_bias: f32, pub shadow_normal_bias: f32, pub cascade_shadow_config: CascadeShadowConfig, @@ -79,6 +83,7 @@ pub struct GpuDirectionalLight { color: Vec4, dir_to_light: Vec3, flags: u32, + soft_shadow_size: f32, shadow_depth_bias: f32, shadow_normal_bias: f32, num_cascades: u32, @@ -134,8 +139,10 @@ pub const MAX_CASCADES_PER_LIGHT: usize = 1; #[derive(Resource, Clone)] pub struct ShadowSamplers { - pub point_light_sampler: Sampler, - pub directional_light_sampler: Sampler, + pub point_light_comparison_sampler: Sampler, + pub point_light_linear_sampler: Sampler, + pub directional_light_comparison_sampler: Sampler, + pub directional_light_linear_sampler: Sampler, } // TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system @@ -143,27 +150,30 @@ impl FromWorld for ShadowSamplers { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); + let base_sampler_descriptor = SamplerDescriptor { + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, + ..default() + }; + ShadowSamplers { - point_light_sampler: render_device.create_sampler(&SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Nearest, + point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor { compare: Some(CompareFunction::GreaterEqual), - ..Default::default() - }), - directional_light_sampler: render_device.create_sampler(&SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Nearest, - compare: Some(CompareFunction::GreaterEqual), - ..Default::default() + ..base_sampler_descriptor }), + point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor), + directional_light_comparison_sampler: render_device.create_sampler( + &SamplerDescriptor { + compare: Some(CompareFunction::GreaterEqual), + ..base_sampler_descriptor + }, + ), + directional_light_linear_sampler: render_device + .create_sampler(&base_sampler_descriptor), } } } @@ -252,11 +262,13 @@ pub fn extract_lights( radius: point_light.radius, transform: *transform, shadows_enabled: point_light.shadows_enabled, + soft_shadows_enabled: point_light.soft_shadows_enabled, shadow_depth_bias: point_light.shadow_depth_bias, // The factor of SQRT_2 is for the worst-case diagonal offset shadow_normal_bias: point_light.shadow_normal_bias * point_light_texel_size * std::f32::consts::SQRT_2, + shadow_map_near_z: point_light.shadow_map_near_z, spot_light_angles: None, }; point_lights_values.push(( @@ -283,7 +295,7 @@ pub fn extract_lights( // However, since exclusive access to the main world in extract is ill-advised, we just clone here. let render_visible_entities = visible_entities.clone(); let texel_size = - 2.0 * spot_light.outer_angle.tan() / directional_light_shadow_map.size as f32; + 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32; spot_lights_values.push(( entity, @@ -301,11 +313,13 @@ pub fn extract_lights( radius: spot_light.radius, transform: *transform, shadows_enabled: spot_light.shadows_enabled, + soft_shadows_enabled: spot_light.soft_shadows_enabled, shadow_depth_bias: spot_light.shadow_depth_bias, // The factor of SQRT_2 is for the worst-case diagonal offset shadow_normal_bias: spot_light.shadow_normal_bias * texel_size * std::f32::consts::SQRT_2, + shadow_map_near_z: spot_light.shadow_map_near_z, spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)), }, render_visible_entities, @@ -342,6 +356,7 @@ pub fn extract_lights( illuminance: directional_light.illuminance, transform: *transform, volumetric: volumetric_light.is_some(), + soft_shadow_size: directional_light.soft_shadow_size, shadows_enabled: directional_light.shadows_enabled, shadow_depth_bias: directional_light.shadow_depth_bias, // The factor of SQRT_2 is for the worst-case diagonal offset @@ -356,8 +371,6 @@ pub fn extract_lights( } } -pub(crate) const POINT_LIGHT_NEAR_Z: f32 = 0.1f32; - pub(crate) struct CubeMapFace { pub(crate) target: Vec3, pub(crate) up: Vec3, @@ -467,10 +480,10 @@ pub fn calculate_cluster_factors( if is_orthographic { Vec2::new(-near, z_slices / (-far - -near)) } else { - let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / (far / near).ln(); + let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near); Vec2::new( z_slices_of_ln_zfar_over_znear, - near.ln() * z_slices_of_ln_zfar_over_znear, + ops::ln(near) * z_slices_of_ln_zfar_over_znear, ) } } @@ -502,9 +515,9 @@ pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 { ) } -pub(crate) fn spot_light_clip_from_view(angle: f32) -> Mat4 { +pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 { // spot light projection FOV is 2x the angle from spot light center to outer edge - Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, POINT_LIGHT_NEAR_Z) + Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, near_z) } #[allow(clippy::too_many_arguments)] @@ -549,8 +562,6 @@ pub fn prepare_lights( }; // Pre-calculate for PointLights - let cube_face_projection = - Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z); let cube_face_rotations = CUBE_MAP_FACES .iter() .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up)) @@ -685,6 +696,12 @@ pub fn prepare_lights( flags |= PointLightFlags::SHADOWS_ENABLED; } + let cube_face_projection = Mat4::perspective_infinite_reverse_rh( + std::f32::consts::FRAC_PI_2, + 1.0, + light.shadow_map_near_z, + ); + let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles { Some((inner, outer)) => { let light_direction = light.transform.forward(); @@ -692,14 +709,14 @@ pub fn prepare_lights( flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE; } - let cos_outer = outer.cos(); - let spot_scale = 1.0 / f32::max(inner.cos() - cos_outer, 1e-4); + let cos_outer = ops::cos(outer); + let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4); let spot_offset = -cos_outer * spot_scale; ( // For spot lights: the direction (x,z), spot_scale and spot_offset light_direction.xz().extend(spot_scale).extend(spot_offset), - outer.tan(), + ops::tan(outer), ) } None => { @@ -727,9 +744,17 @@ pub fn prepare_lights( .extend(1.0 / (light.range * light.range)), position_radius: light.transform.translation().extend(light.radius), flags: flags.bits(), + soft_shadow_size: if light.soft_shadows_enabled { + light.radius + } else { + 0.0 + }, shadow_depth_bias: light.shadow_depth_bias, shadow_normal_bias: light.shadow_normal_bias, + shadow_map_near_z: light.shadow_map_near_z, spot_light_tan_angle, + pad_a: 0.0, + pad_b: 0.0, }); global_light_meta.entity_to_index.insert(entity, index); } @@ -771,6 +796,7 @@ pub fn prepare_lights( // direction is negated to be ready for N.L dir_to_light: light.transform.back().into(), flags: flags.bits(), + soft_shadow_size: light.soft_shadow_size.unwrap_or_default(), shadow_depth_bias: light.shadow_depth_bias, shadow_normal_bias: light.shadow_normal_bias, num_cascades: num_cascades as u32, @@ -878,6 +904,12 @@ pub fn prepare_lights( // and ignore rotation because we want the shadow map projections to align with the axes let view_translation = GlobalTransform::from_translation(light.transform.translation()); + let cube_face_projection = Mat4::perspective_infinite_reverse_rh( + std::f32::consts::FRAC_PI_2, + 1.0, + light.shadow_map_near_z, + ); + for (face_index, (view_rotation, frustum)) in cube_face_rotations .iter() .zip(&point_light_frusta.unwrap().frusta) @@ -946,7 +978,7 @@ pub fn prepare_lights( let angle = light.spot_light_angles.expect("lights should be sorted so that \ [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1; - let spot_projection = spot_light_clip_from_view(angle); + let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z); let depth_texture_view = directional_light_depth_texture diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a7bbb7515a..007ed983e5 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1837,11 +1837,11 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), - 21, + 23, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), - 22, + 24, )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 4891588552..cda05314fb 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -4,7 +4,6 @@ use bevy_math::Mat4; use bevy_render::{ mesh::morph::MAX_MORPH_WEIGHTS, render_resource::*, renderer::RenderDevice, texture::GpuImage, }; -use std::mem::size_of; use crate::render::skin::MAX_JOINTS; diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index d0a506e9c3..9446b3c9a9 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -213,11 +213,13 @@ fn layout_entries( ))] texture_cube(TextureSampleType::Depth), ), - // Point Shadow Texture Array Sampler + // Point Shadow Texture Array Comparison Sampler (3, sampler(SamplerBindingType::Comparison)), + // Point Shadow Texture Array Linear Sampler + (4, sampler(SamplerBindingType::Filtering)), // Directional Shadow Texture Array ( - 4, + 5, #[cfg(any( not(feature = "webgl"), not(target_arch = "wasm32"), @@ -227,11 +229,13 @@ fn layout_entries( #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] texture_2d(TextureSampleType::Depth), ), - // Directional Shadow Texture Array Sampler - (5, sampler(SamplerBindingType::Comparison)), - // ClusterableObjects + // Directional Shadow Texture Array Comparison Sampler + (6, sampler(SamplerBindingType::Comparison)), + // Directional Shadow Texture Array Linear Sampler + (7, sampler(SamplerBindingType::Filtering)), + // PointLights ( - 6, + 8, buffer_layout( clustered_forward_buffer_binding_type, false, @@ -242,7 +246,7 @@ fn layout_entries( ), // ClusteredLightIndexLists ( - 7, + 9, buffer_layout( clustered_forward_buffer_binding_type, false, @@ -255,7 +259,7 @@ fn layout_entries( ), // ClusterOffsetsAndCounts ( - 8, + 10, buffer_layout( clustered_forward_buffer_binding_type, false, @@ -266,16 +270,16 @@ fn layout_entries( ), // Globals ( - 9, + 11, uniform_buffer::(false).visibility(ShaderStages::VERTEX_FRAGMENT), ), // Fog - (10, uniform_buffer::(true)), + (12, uniform_buffer::(true)), // Light probes - (11, uniform_buffer::(true)), + (13, uniform_buffer::(true)), // Visibility ranges ( - 12, + 14, buffer_layout( visibility_ranges_buffer_binding_type, false, @@ -284,10 +288,10 @@ fn layout_entries( .visibility(ShaderStages::VERTEX), ), // Screen space reflection settings - (13, uniform_buffer::(true)), + (15, uniform_buffer::(true)), // Screen space ambient occlusion texture ( - 14, + 16, texture_2d(TextureSampleType::Float { filterable: false }), ), ), @@ -296,10 +300,10 @@ fn layout_entries( // EnvironmentMapLight let environment_map_entries = environment_map::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( - (15, environment_map_entries[0]), - (16, environment_map_entries[1]), - (17, environment_map_entries[2]), - (18, environment_map_entries[3]), + (17, environment_map_entries[0]), + (18, environment_map_entries[1]), + (19, environment_map_entries[2]), + (20, environment_map_entries[3]), )); // Irradiance volumes @@ -307,16 +311,16 @@ fn layout_entries( let irradiance_volume_entries = irradiance_volume::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( - (19, irradiance_volume_entries[0]), - (20, irradiance_volume_entries[1]), + (21, irradiance_volume_entries[0]), + (22, irradiance_volume_entries[1]), )); } // Tonemapping let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); entries = entries.extend_with_indices(( - (21, tonemapping_lut_entries[0]), - (22, tonemapping_lut_entries[1]), + (23, tonemapping_lut_entries[0]), + (24, tonemapping_lut_entries[1]), )); // Prepass @@ -326,7 +330,7 @@ fn layout_entries( { for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key) .iter() - .zip([23, 24, 25, 26]) + .zip([25, 26, 27, 28]) { if let Some(entry) = entry { entries = entries.extend_with_indices(((binding as u32, *entry),)); @@ -337,10 +341,10 @@ fn layout_entries( // View Transmission Texture entries = entries.extend_with_indices(( ( - 27, + 29, texture_2d(TextureSampleType::Float { filterable: true }), ), - (28, sampler(SamplerBindingType::Filtering)), + (30, sampler(SamplerBindingType::Filtering)), )); entries.to_vec() @@ -527,23 +531,25 @@ pub fn prepare_mesh_view_bind_groups( (0, view_binding.clone()), (1, light_binding.clone()), (2, &shadow_bindings.point_light_depth_texture_view), - (3, &shadow_samplers.point_light_sampler), - (4, &shadow_bindings.directional_light_depth_texture_view), - (5, &shadow_samplers.directional_light_sampler), - (6, clusterable_objects_binding.clone()), + (3, &shadow_samplers.point_light_comparison_sampler), + (4, &shadow_samplers.point_light_linear_sampler), + (5, &shadow_bindings.directional_light_depth_texture_view), + (6, &shadow_samplers.directional_light_comparison_sampler), + (7, &shadow_samplers.directional_light_linear_sampler), + (8, clusterable_objects_binding.clone()), ( - 7, + 9, cluster_bindings .clusterable_object_index_lists_binding() .unwrap(), ), - (8, cluster_bindings.offsets_and_counts_binding().unwrap()), - (9, globals.clone()), - (10, fog_binding.clone()), - (11, light_probes_binding.clone()), - (12, visibility_ranges_buffer.as_entire_binding()), - (13, ssr_binding.clone()), - (14, ssao_view), + (10, cluster_bindings.offsets_and_counts_binding().unwrap()), + (11, globals.clone()), + (12, fog_binding.clone()), + (13, light_probes_binding.clone()), + (14, visibility_ranges_buffer.as_entire_binding()), + (15, ssr_binding.clone()), + (16, ssao_view), )); let environment_map_bind_group_entries = RenderViewEnvironmentMapBindGroupEntries::get( @@ -560,10 +566,10 @@ pub fn prepare_mesh_view_bind_groups( sampler, } => { entries = entries.extend_with_indices(( - (15, diffuse_texture_view), - (16, specular_texture_view), - (17, sampler), - (18, environment_map_binding.clone()), + (17, diffuse_texture_view), + (18, specular_texture_view), + (19, sampler), + (20, environment_map_binding.clone()), )); } RenderViewEnvironmentMapBindGroupEntries::Multiple { @@ -572,10 +578,10 @@ pub fn prepare_mesh_view_bind_groups( sampler, } => { entries = entries.extend_with_indices(( - (15, diffuse_texture_views.as_slice()), - (16, specular_texture_views.as_slice()), - (17, sampler), - (18, environment_map_binding.clone()), + (17, diffuse_texture_views.as_slice()), + (18, specular_texture_views.as_slice()), + (19, sampler), + (20, environment_map_binding.clone()), )); } } @@ -596,21 +602,21 @@ pub fn prepare_mesh_view_bind_groups( texture_view, sampler, }) => { - entries = entries.extend_with_indices(((19, texture_view), (20, sampler))); + entries = entries.extend_with_indices(((21, texture_view), (22, sampler))); } Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple { ref texture_views, sampler, }) => { entries = entries - .extend_with_indices(((19, texture_views.as_slice()), (20, sampler))); + .extend_with_indices(((21, texture_views.as_slice()), (22, sampler))); } None => {} } let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); - entries = entries.extend_with_indices(((21, lut_bindings.0), (22, lut_bindings.1))); + entries = entries.extend_with_indices(((23, lut_bindings.0), (24, lut_bindings.1))); // When using WebGL, we can't have a depth texture with multisampling let prepass_bindings; @@ -620,7 +626,7 @@ pub fn prepare_mesh_view_bind_groups( for (binding, index) in prepass_bindings .iter() .map(Option::as_ref) - .zip([23, 24, 25, 26]) + .zip([25, 26, 27, 28]) .flat_map(|(b, i)| b.map(|b| (b, i))) { entries = entries.extend_with_indices(((index, binding),)); @@ -636,7 +642,7 @@ pub fn prepare_mesh_view_bind_groups( .unwrap_or(&fallback_image_zero.sampler); entries = - entries.extend_with_indices(((27, transmission_view), (28, transmission_sampler))); + entries.extend_with_indices(((29, transmission_view), (30, transmission_sampler))); commands.entity(entity).insert(MeshViewBindGroup { value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 5036e81673..dfda3a576b 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -13,88 +13,91 @@ #else @group(0) @binding(2) var point_shadow_textures: texture_depth_cube_array; #endif -@group(0) @binding(3) var point_shadow_textures_sampler: sampler_comparison; +@group(0) @binding(3) var point_shadow_textures_comparison_sampler: sampler_comparison; +@group(0) @binding(4) var point_shadow_textures_linear_sampler: sampler; #ifdef NO_ARRAY_TEXTURES_SUPPORT -@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d; +@group(0) @binding(5) var directional_shadow_textures: texture_depth_2d; #else -@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d_array; +@group(0) @binding(5) var directional_shadow_textures: texture_depth_2d_array; #endif -@group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison; +@group(0) @binding(6) var directional_shadow_textures_comparison_sampler: sampler_comparison; +@group(0) @binding(7) var directional_shadow_textures_linear_sampler: sampler; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 -@group(0) @binding(6) var clusterable_objects: types::ClusterableObjects; -@group(0) @binding(7) var clusterable_object_index_lists: types::ClusterLightIndexLists; -@group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; +@group(0) @binding(8) var clusterable_objects: types::ClusterableObjects; +@group(0) @binding(9) var clusterable_object_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(10) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #else -@group(0) @binding(6) var clusterable_objects: types::ClusterableObjects; -@group(0) @binding(7) var clusterable_object_index_lists: types::ClusterLightIndexLists; -@group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; +@group(0) @binding(8) var clusterable_objects: types::ClusterableObjects; +@group(0) @binding(9) var clusterable_object_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(10) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #endif -@group(0) @binding(9) var globals: Globals; -@group(0) @binding(10) var fog: types::Fog; -@group(0) @binding(11) var light_probes: types::LightProbes; +@group(0) @binding(11) var globals: Globals; +@group(0) @binding(12) var fog: types::Fog; +@group(0) @binding(13) var light_probes: types::LightProbes; const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6 -@group(0) @binding(12) var visibility_ranges: array>; +@group(0) @binding(14) var visibility_ranges: array>; #else -@group(0) @binding(12) var visibility_ranges: array, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>; +@group(0) @binding(14) var visibility_ranges: array, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>; #endif -@group(0) @binding(13) var ssr_settings: types::ScreenSpaceReflectionsSettings; -@group(0) @binding(14) var screen_space_ambient_occlusion_texture: texture_2d; +@group(0) @binding(15) var ssr_settings: types::ScreenSpaceReflectionsSettings; +@group(0) @binding(16) var screen_space_ambient_occlusion_texture: texture_2d; #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(15) var diffuse_environment_maps: binding_array, 8u>; -@group(0) @binding(16) var specular_environment_maps: binding_array, 8u>; +@group(0) @binding(17) var diffuse_environment_maps: binding_array, 8u>; +@group(0) @binding(18) var specular_environment_maps: binding_array, 8u>; #else -@group(0) @binding(15) var diffuse_environment_map: texture_cube; -@group(0) @binding(16) var specular_environment_map: texture_cube; +@group(0) @binding(17) var diffuse_environment_map: texture_cube; +@group(0) @binding(18) var specular_environment_map: texture_cube; #endif -@group(0) @binding(17) var environment_map_sampler: sampler; -@group(0) @binding(18) var environment_map_uniform: types::EnvironmentMapUniform; +@group(0) @binding(19) var environment_map_sampler: sampler; +@group(0) @binding(20) var environment_map_uniform: types::EnvironmentMapUniform; #ifdef IRRADIANCE_VOLUMES_ARE_USABLE #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(19) var irradiance_volumes: binding_array, 8u>; +@group(0) @binding(21) var irradiance_volumes: binding_array, 8u>; #else -@group(0) @binding(19) var irradiance_volume: texture_3d; +@group(0) @binding(21) var irradiance_volume: texture_3d; #endif -@group(0) @binding(20) var irradiance_volume_sampler: sampler; +@group(0) @binding(22) var irradiance_volume_sampler: sampler; #endif -@group(0) @binding(21) var dt_lut_texture: texture_3d; -@group(0) @binding(22) var dt_lut_sampler: sampler; +// NB: If you change these, make sure to update `tonemapping_shared.wgsl` too. +@group(0) @binding(23) var dt_lut_texture: texture_3d; +@group(0) @binding(24) var dt_lut_sampler: sampler; #ifdef MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(23) var depth_prepass_texture: texture_depth_multisampled_2d; +@group(0) @binding(25) var depth_prepass_texture: texture_depth_multisampled_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(24) var normal_prepass_texture: texture_multisampled_2d; +@group(0) @binding(26) var normal_prepass_texture: texture_multisampled_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(25) var motion_vector_prepass_texture: texture_multisampled_2d; +@group(0) @binding(27) var motion_vector_prepass_texture: texture_multisampled_2d; #endif // MOTION_VECTOR_PREPASS #else // MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(23) var depth_prepass_texture: texture_depth_2d; +@group(0) @binding(25) var depth_prepass_texture: texture_depth_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(24) var normal_prepass_texture: texture_2d; +@group(0) @binding(26) var normal_prepass_texture: texture_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(25) var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(27) var motion_vector_prepass_texture: texture_2d; #endif // MOTION_VECTOR_PREPASS #endif // MULTISAMPLED #ifdef DEFERRED_PREPASS -@group(0) @binding(26) var deferred_prepass_texture: texture_2d; +@group(0) @binding(28) var deferred_prepass_texture: texture_2d; #endif // DEFERRED_PREPASS -@group(0) @binding(27) var view_transmission_texture: texture_2d; -@group(0) @binding(28) var view_transmission_sampler: sampler; +@group(0) @binding(29) var view_transmission_texture: texture_2d; +@group(0) @binding(30) var view_transmission_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index c1d379e3b4..a01b135093 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -11,6 +11,10 @@ struct ClusterableObject { shadow_depth_bias: f32, shadow_normal_bias: f32, spot_light_tan_angle: f32, + soft_shadow_size: f32, + shadow_map_near_z: f32, + pad_a: f32, + pad_b: f32, }; const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; @@ -28,6 +32,7 @@ struct DirectionalLight { direction_to_light: vec3, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, + soft_shadow_size: f32, shadow_depth_bias: f32, shadow_normal_bias: f32, num_cascades: u32, @@ -139,7 +144,7 @@ struct LightProbes { // Settings for screen space reflections. // // For more information on these settings, see the documentation for -// `bevy_pbr::ssr::ScreenSpaceReflectionsSettings`. +// `bevy_pbr::ssr::ScreenSpaceReflections`. struct ScreenSpaceReflectionsSettings { perceptual_roughness_threshold: f32, thickness: f32, diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index d3d43e9b69..2f28fbf588 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,4 +1,4 @@ -use std::{iter, mem, mem::size_of}; +use std::{iter, mem}; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 35e683c4ff..511d223624 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -447,8 +447,14 @@ fn apply_pbr_lighting( var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal); + && (view_bindings::clusterable_objects.data[light_id].flags & + mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + shadow = shadows::fetch_spot_shadow( + light_id, + in.world_position, + in.world_normal, + view_bindings::clusterable_objects.data[light_id].shadow_map_near_z, + ); } let light_contrib = lighting::spot_light(light_id, &lighting_input); @@ -467,7 +473,12 @@ fn apply_pbr_lighting( var transmitted_shadow: f32 = 1.0; if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { - transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); + transmitted_shadow = shadows::fetch_spot_shadow( + light_id, + diffuse_transmissive_lobe_world_position, + -in.world_normal, + view_bindings::clusterable_objects.data[light_id].shadow_map_near_z, + ); } let transmitted_light_contrib = @@ -545,19 +556,20 @@ fn apply_pbr_lighting( // example, both lightmaps and irradiance volumes are present. var indirect_light = vec3(0.0f); + var found_diffuse_indirect = false; #ifdef LIGHTMAP - if (all(indirect_light == vec3(0.0f))) { - indirect_light += in.lightmap_light * diffuse_color; - } + indirect_light += in.lightmap_light * diffuse_color; + found_diffuse_indirect = true; #endif -#ifdef IRRADIANCE_VOLUME { +#ifdef IRRADIANCE_VOLUME // Irradiance volume light (indirect) - if (all(indirect_light == vec3(0.0f))) { + if (!found_diffuse_indirect) { let irradiance_volume_light = irradiance_volume::irradiance_volume_light( in.world_position.xyz, in.N); indirect_light += irradiance_volume_light * diffuse_color * diffuse_occlusion; + found_diffuse_indirect = true; } #endif @@ -574,7 +586,7 @@ fn apply_pbr_lighting( let environment_light = environment_map::environment_map_light( environment_map_lighting_input, - any(indirect_light != vec3(0.0f)) + found_diffuse_indirect ); // If screen space reflections are going to be used for this material, don't @@ -589,7 +601,7 @@ fn apply_pbr_lighting( if (!use_ssr) { let environment_light = environment_map::environment_map_light( &lighting_input, - any(indirect_light != vec3(0.0f)) + found_diffuse_indirect ); indirect_light += environment_light.diffuse * diffuse_occlusion + diff --git a/crates/bevy_pbr/src/render/shadow_sampling.wgsl b/crates/bevy_pbr/src/render/shadow_sampling.wgsl index ec155cf3fc..0c546a37c6 100644 --- a/crates/bevy_pbr/src/render/shadow_sampling.wgsl +++ b/crates/bevy_pbr/src/render/shadow_sampling.wgsl @@ -12,14 +12,14 @@ fn sample_shadow_map_hardware(light_local: vec2, depth: f32, array_index: i #ifdef NO_ARRAY_TEXTURES_SUPPORT return textureSampleCompare( view_bindings::directional_shadow_textures, - view_bindings::directional_shadow_textures_sampler, + view_bindings::directional_shadow_textures_comparison_sampler, light_local, depth, ); #else return textureSampleCompareLevel( view_bindings::directional_shadow_textures, - view_bindings::directional_shadow_textures_sampler, + view_bindings::directional_shadow_textures_comparison_sampler, light_local, array_index, depth, @@ -27,6 +27,40 @@ fn sample_shadow_map_hardware(light_local: vec2, depth: f32, array_index: i #endif } +// Does a single sample of the blocker search, a part of the PCSS algorithm. +// This is the variant used for directional lights. +fn search_for_blockers_in_shadow_map_hardware( + light_local: vec2, + depth: f32, + array_index: i32, +) -> vec2 { +#ifdef WEBGL2 + // Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled + // with different samplers, or it'll blow up. + return vec2(0.0); +#else // WEBGL2 + +#ifdef NO_ARRAY_TEXTURES_SUPPORT + let sampled_depth = textureSampleLevel( + view_bindings::directional_shadow_textures, + view_bindings::directional_shadow_textures_linear_sampler, + light_local, + 0.0, + ); +#else // NO_ARRAY_TEXTURES_SUPPORT + let sampled_depth = textureSampleLevel( + view_bindings::directional_shadow_textures, + view_bindings::directional_shadow_textures_linear_sampler, + light_local, + array_index, + 0.0, + ); +#endif // NO_ARRAY_TEXTURES_SUPPORT + return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth); + +#endif // WEBGL2 +} + // Numbers determined by trial and error that gave nice results. const SPOT_SHADOW_TEXEL_SIZE: f32 = 0.0134277345; const POINT_SHADOW_SCALE: f32 = 0.003; @@ -113,9 +147,9 @@ fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 { // Creates a random rotation matrix using interleaved gradient noise. // // See: https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/ -fn random_rotation_matrix(scale: vec2) -> mat2x2 { +fn random_rotation_matrix(scale: vec2, temporal: bool) -> mat2x2 { let random_angle = 2.0 * PI * interleaved_gradient_noise( - scale, view_bindings::globals.frame_count); + scale, select(1u, view_bindings::globals.frame_count, temporal)); let m = vec2(sin(random_angle), cos(random_angle)); return mat2x2( m.y, -m.x, @@ -123,13 +157,28 @@ fn random_rotation_matrix(scale: vec2) -> mat2x2 { ); } -fn sample_shadow_map_jimenez_fourteen(light_local: vec2, depth: f32, array_index: i32, texel_size: f32) -> f32 { +// Calculates the distance between spiral samples for the given texel size and +// penumbra size. This is used for the Jimenez '14 (i.e. temporal) variant of +// shadow sampling. +fn calculate_uv_offset_scale_jimenez_fourteen(texel_size: f32, blur_size: f32) -> vec2 { let shadow_map_size = vec2(textureDimensions(view_bindings::directional_shadow_textures)); - let rotation_matrix = random_rotation_matrix(light_local * shadow_map_size); // Empirically chosen fudge factor to make PCF look better across different CSM cascades let f = map(0.00390625, 0.022949219, 0.015, 0.035, texel_size); - let uv_offset_scale = f / (texel_size * shadow_map_size); + return f * blur_size / (texel_size * shadow_map_size); +} + +fn sample_shadow_map_jimenez_fourteen( + light_local: vec2, + depth: f32, + array_index: i32, + texel_size: f32, + blur_size: f32, + temporal: bool, +) -> f32 { + let shadow_map_size = vec2(textureDimensions(view_bindings::directional_shadow_textures)); + let rotation_matrix = random_rotation_matrix(light_local * shadow_map_size, temporal); + let uv_offset_scale = calculate_uv_offset_scale_jimenez_fourteen(texel_size, blur_size); // https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135) let sample_offset0 = (rotation_matrix * utils::SPIRAL_OFFSET_0_) * uv_offset_scale; @@ -153,11 +202,57 @@ fn sample_shadow_map_jimenez_fourteen(light_local: vec2, depth: f32, array_ return sum / 8.0; } +// Performs the blocker search portion of percentage-closer soft shadows (PCSS). +// This is the variation used for directional lights. +// +// We can't use Castano '13 here because that has a hard-wired fixed size, while +// the PCSS algorithm requires a search size that varies based on the size of +// the light. So we instead use the D3D sample point positions, spaced according +// to the search size, to provide a sample pattern in a similar manner to the +// cubemap sampling approach we use for PCF. +// +// `search_size` is the size of the search region in texels. +fn search_for_blockers_in_shadow_map( + light_local: vec2, + depth: f32, + array_index: i32, + texel_size: f32, + search_size: f32, +) -> f32 { + let shadow_map_size = vec2(textureDimensions(view_bindings::directional_shadow_textures)); + let uv_offset_scale = search_size / (texel_size * shadow_map_size); + + let offset0 = D3D_SAMPLE_POINT_POSITIONS[0] * uv_offset_scale; + let offset1 = D3D_SAMPLE_POINT_POSITIONS[1] * uv_offset_scale; + let offset2 = D3D_SAMPLE_POINT_POSITIONS[2] * uv_offset_scale; + let offset3 = D3D_SAMPLE_POINT_POSITIONS[3] * uv_offset_scale; + let offset4 = D3D_SAMPLE_POINT_POSITIONS[4] * uv_offset_scale; + let offset5 = D3D_SAMPLE_POINT_POSITIONS[5] * uv_offset_scale; + let offset6 = D3D_SAMPLE_POINT_POSITIONS[6] * uv_offset_scale; + let offset7 = D3D_SAMPLE_POINT_POSITIONS[7] * uv_offset_scale; + + var sum = vec2(0.0); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset0, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset1, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset2, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset3, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset4, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset5, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset6, depth, array_index); + sum += search_for_blockers_in_shadow_map_hardware(light_local + offset7, depth, array_index); + + if (sum.y == 0.0) { + return 0.0; + } + return sum.x / sum.y; +} + fn sample_shadow_map(light_local: vec2, depth: f32, array_index: i32, texel_size: f32) -> f32 { #ifdef SHADOW_FILTER_METHOD_GAUSSIAN return sample_shadow_map_castano_thirteen(light_local, depth, array_index); #else ifdef SHADOW_FILTER_METHOD_TEMPORAL - return sample_shadow_map_jimenez_fourteen(light_local, depth, array_index, texel_size); + return sample_shadow_map_jimenez_fourteen( + light_local, depth, array_index, texel_size, 1.0, true); #else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2 return sample_shadow_map_hardware(light_local, depth, array_index); #else @@ -169,6 +264,45 @@ fn sample_shadow_map(light_local: vec2, depth: f32, array_index: i32, texel #endif } +// Samples the shadow map for a directional light when percentage-closer soft +// shadows are being used. +// +// We first search for a *blocker*, which is the average depth value of any +// shadow map samples that are adjacent to the sample we're considering. That +// allows us to determine the penumbra size; a larger gap between the blocker +// and the depth of this sample results in a wider penumbra. Finally, we sample +// the shadow map the same way we do in PCF, using that penumbra width. +// +// A good overview of the technique: +// +fn sample_shadow_map_pcss( + light_local: vec2, + depth: f32, + array_index: i32, + texel_size: f32, + light_size: f32, +) -> f32 { + // Determine the average Z value of the closest blocker. + let z_blocker = search_for_blockers_in_shadow_map( + light_local, depth, array_index, texel_size, light_size); + + // Don't let the blur size go below 0.5, or shadows will look unacceptably aliased. + let blur_size = max((z_blocker - depth) * light_size / depth, 0.5); + + // FIXME: We can't use Castano '13 here because that has a hard-wired fixed + // size. So we instead use Jimenez '14 unconditionally. In the non-temporal + // variant this is unfortunately rather noisy. This may be improvable in the + // future by generating a mip chain of the shadow map and using that to + // provide better blurs. +#ifdef SHADOW_FILTER_METHOD_TEMPORAL + return sample_shadow_map_jimenez_fourteen( + light_local, depth, array_index, texel_size, blur_size, true); +#else // SHADOW_FILTER_METHOD_TEMPORAL + return sample_shadow_map_jimenez_fourteen( + light_local, depth, array_index, texel_size, blur_size, false); +#endif // SHADOW_FILTER_METHOD_TEMPORAL +} + // NOTE: Due to the non-uniform control flow in `shadows::fetch_point_shadow`, // we must use the Level variant of textureSampleCompare to avoid undefined // behavior due to some of the fragments in a quad (2x2 fragments) being @@ -176,12 +310,56 @@ fn sample_shadow_map(light_local: vec2, depth: f32, array_index: i32, texel // The shadow maps have no mipmaps so Level just samples from LOD 0. fn sample_shadow_cubemap_hardware(light_local: vec3, depth: f32, light_id: u32) -> f32 { #ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT - return textureSampleCompare(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, light_local, depth); + return textureSampleCompare( + view_bindings::point_shadow_textures, + view_bindings::point_shadow_textures_comparison_sampler, + light_local, + depth + ); #else - return textureSampleCompareLevel(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, light_local, i32(light_id), depth); + return textureSampleCompareLevel( + view_bindings::point_shadow_textures, + view_bindings::point_shadow_textures_comparison_sampler, + light_local, + i32(light_id), + depth + ); #endif } +// Performs one sample of the blocker search. This variation of the blocker +// search function is for point and spot lights. +fn search_for_blockers_in_shadow_cubemap_hardware( + light_local: vec3, + depth: f32, + light_id: u32, +) -> vec2 { +#ifdef WEBGL2 + // Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled + // with different samplers, or it'll blow up. + return vec2(0.0); +#else // WEBGL2 + +#ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT + let sampled_depth = textureSample( + view_bindings::point_shadow_textures, + view_bindings::point_shadow_textures_linear_sampler, + light_local, + ); +#else + let sampled_depth = textureSample( + view_bindings::point_shadow_textures, + view_bindings::point_shadow_textures_linear_sampler, + light_local, + i32(light_id), + ); +#endif + + return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth); + +#endif // WEBGL2 +} + fn sample_shadow_cubemap_at_offset( position: vec2, coeff: f32, @@ -198,6 +376,26 @@ fn sample_shadow_cubemap_at_offset( ) * coeff; } +// Computes the search position and performs one sample of the blocker search. +// This variation of the blocker search function is for point and spot lights. +// +// `x_basis`, `y_basis`, and `light_local` form an orthonormal basis over which +// the blocker search happens. +fn search_for_blockers_in_shadow_cubemap_at_offset( + position: vec2, + x_basis: vec3, + y_basis: vec3, + light_local: vec3, + depth: f32, + light_id: u32, +) -> vec2 { + return search_for_blockers_in_shadow_cubemap_hardware( + light_local + position.x * x_basis + position.y * y_basis, + depth, + light_id + ); +} + // This more or less does what Castano13 does, but in 3D space. Castano13 is // essentially an optimized 2D Gaussian filter that takes advantage of the // bilinear filtering hardware to reduce the number of samples needed. This @@ -249,12 +447,13 @@ fn sample_shadow_cubemap_gaussian( // This is a port of the Jimenez14 filter above to the 3D space. It jitters the // points in the spiral pattern after first creating a 2D orthonormal basis // along the principal light direction. -fn sample_shadow_cubemap_temporal( +fn sample_shadow_cubemap_jittered( light_local: vec3, depth: f32, scale: f32, distance_to_light: f32, light_id: u32, + temporal: bool, ) -> f32 { // Create an orthonormal basis so we can apply a 2D sampling pattern to a // cubemap. @@ -264,7 +463,7 @@ fn sample_shadow_cubemap_temporal( } let basis = orthonormalize(light_local, up) * scale * distance_to_light; - let rotation_matrix = random_rotation_matrix(vec2(1.0)); + let rotation_matrix = random_rotation_matrix(vec2(1.0), temporal); let sample_offset0 = rotation_matrix * utils::SPIRAL_OFFSET_0_ * POINT_SHADOW_TEMPORAL_OFFSET_SCALE; @@ -313,8 +512,8 @@ fn sample_shadow_cubemap( return sample_shadow_cubemap_gaussian( light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id); #else ifdef SHADOW_FILTER_METHOD_TEMPORAL - return sample_shadow_cubemap_temporal( - light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id); + return sample_shadow_cubemap_jittered( + light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id, true); #else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2 return sample_shadow_cubemap_hardware(light_local, depth, light_id); #else @@ -325,3 +524,76 @@ fn sample_shadow_cubemap( return 0.0; #endif } + +// Searches for PCSS blockers in a cubemap. This is the variant of the blocker +// search used for point and spot lights. +// +// This follows the logic in `sample_shadow_cubemap_gaussian`, but uses linear +// sampling instead of percentage-closer filtering. +// +// The `scale` parameter represents the size of the light. +fn search_for_blockers_in_shadow_cubemap( + light_local: vec3, + depth: f32, + scale: f32, + distance_to_light: f32, + light_id: u32, +) -> f32 { + // Create an orthonormal basis so we can apply a 2D sampling pattern to a + // cubemap. + var up = vec3(0.0, 1.0, 0.0); + if (dot(up, normalize(light_local)) > 0.99) { + up = vec3(1.0, 0.0, 0.0); // Avoid creating a degenerate basis. + } + let basis = orthonormalize(light_local, up) * scale * distance_to_light; + + var sum: vec2 = vec2(0.0); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[0], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[1], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[2], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[3], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[4], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[5], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[6], basis[0], basis[1], light_local, depth, light_id); + sum += search_for_blockers_in_shadow_cubemap_at_offset( + D3D_SAMPLE_POINT_POSITIONS[7], basis[0], basis[1], light_local, depth, light_id); + + if (sum.y == 0.0) { + return 0.0; + } + return sum.x / sum.y; +} + +// Samples the shadow map for a point or spot light when percentage-closer soft +// shadows are being used. +// +// A good overview of the technique: +// +fn sample_shadow_cubemap_pcss( + light_local: vec3, + distance_to_light: f32, + depth: f32, + light_id: u32, + light_size: f32, +) -> f32 { + let z_blocker = search_for_blockers_in_shadow_cubemap( + light_local, depth, light_size, distance_to_light, light_id); + + // Don't let the blur size go below 0.5, or shadows will look unacceptably aliased. + let blur_size = max((z_blocker - depth) * light_size / depth, 0.5); + +#ifdef SHADOW_FILTER_METHOD_TEMPORAL + return sample_shadow_cubemap_jittered( + light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, true); +#else + return sample_shadow_cubemap_jittered( + light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, false); +#endif +} diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index 110d8c7fff..0e539f0009 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -3,7 +3,10 @@ #import bevy_pbr::{ mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE, mesh_view_bindings as view_bindings, - shadow_sampling::{SPOT_SHADOW_TEXEL_SIZE, sample_shadow_cubemap, sample_shadow_map} + shadow_sampling::{ + SPOT_SHADOW_TEXEL_SIZE, sample_shadow_cubemap, sample_shadow_cubemap_pcss, + sample_shadow_map, sample_shadow_map_pcss, + } } #import bevy_render::{ @@ -41,12 +44,30 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v let zw = -major_axis_magnitude * (*light).light_custom_data.xy + (*light).light_custom_data.zw; let depth = zw.x / zw.y; - // Do the lookup, using HW PCF and comparison. Cubemaps assume a left-handed coordinate space, - // so we have to flip the z-axis when sampling. + // If soft shadows are enabled, use the PCSS path. Cubemaps assume a + // left-handed coordinate space, so we have to flip the z-axis when + // sampling. + if ((*light).soft_shadow_size > 0.0) { + return sample_shadow_cubemap_pcss( + frag_ls * flip_z, + distance_to_light, + depth, + light_id, + (*light).soft_shadow_size, + ); + } + + // Do the lookup, using HW PCF and comparison. Cubemaps assume a left-handed + // coordinate space, so we have to flip the z-axis when sampling. return sample_shadow_cubemap(frag_ls * flip_z, distance_to_light, depth, light_id); } -fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { +fn fetch_spot_shadow( + light_id: u32, + frag_position: vec4, + surface_normal: vec3, + near_z: f32, +) -> f32 { let light = &view_bindings::clusterable_objects.data[light_id]; let surface_to_light = (*light).position_radius.xyz - frag_position.xyz; @@ -91,15 +112,16 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: ve // convert to uv coordinates let shadow_uv = shadow_xy_ndc * vec2(0.5, -0.5) + vec2(0.5, 0.5); - // 0.1 must match POINT_LIGHT_NEAR_Z - let depth = 0.1 / -projected_position.z; + let depth = near_z / -projected_position.z; - return sample_shadow_map( - shadow_uv, - depth, - i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset, - SPOT_SHADOW_TEXEL_SIZE - ); + // If soft shadows are enabled, use the PCSS path. + let array_index = i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset; + if ((*light).soft_shadow_size > 0.0) { + return sample_shadow_map_pcss( + shadow_uv, depth, array_index, SPOT_SHADOW_TEXEL_SIZE, (*light).soft_shadow_size); + } + + return sample_shadow_map(shadow_uv, depth, array_index, SPOT_SHADOW_TEXEL_SIZE); } fn get_cascade_index(light_id: u32, view_z: f32) -> u32 { @@ -146,7 +168,12 @@ fn world_to_directional_light_local( return vec4(light_local, depth, 1.0); } -fn sample_directional_cascade(light_id: u32, cascade_index: u32, frag_position: vec4, surface_normal: vec3) -> f32 { +fn sample_directional_cascade( + light_id: u32, + cascade_index: u32, + frag_position: vec4, + surface_normal: vec3, +) -> f32 { let light = &view_bindings::lights.directional_lights[light_id]; let cascade = &(*light).cascades[cascade_index]; @@ -161,7 +188,15 @@ fn sample_directional_cascade(light_id: u32, cascade_index: u32, frag_position: } let array_index = i32((*light).depth_texture_base_index + cascade_index); - return sample_shadow_map(light_local.xy, light_local.z, array_index, (*cascade).texel_size); + let texel_size = (*cascade).texel_size; + + // If soft shadows are enabled, use the PCSS path. + if ((*light).soft_shadow_size > 0.0) { + return sample_shadow_map_pcss( + light_local.xy, light_local.z, array_index, texel_size, (*light).soft_shadow_size); + } + + return sample_shadow_map(light_local.xy, light_local.z, array_index, texel_size); } fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3, view_z: f32) -> f32 { diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index cba48b8f8b..249a2267b7 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ system::{Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ camera::{ExtractedCamera, TemporalJitter}, @@ -68,7 +69,7 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { Shader::from_wgsl ); - app.register_type::(); + app.register_type::(); } fn finish(&self, app: &mut App) { @@ -129,7 +130,7 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { /// Bundle to apply screen space ambient occlusion. #[derive(Bundle, Default, Clone)] pub struct ScreenSpaceAmbientOcclusionBundle { - pub settings: ScreenSpaceAmbientOcclusionSettings, + pub settings: ScreenSpaceAmbientOcclusion, pub depth_prepass: DepthPrepass, pub normal_prepass: NormalPrepass, } @@ -149,16 +150,20 @@ pub struct ScreenSpaceAmbientOcclusionBundle { /// and add the [`DepthPrepass`] and [`NormalPrepass`] components to your camera. /// /// It strongly recommended that you use SSAO in conjunction with -/// TAA ([`bevy_core_pipeline::experimental::taa::TemporalAntiAliasSettings`]). +/// TAA ([`bevy_core_pipeline::experimental::taa::TemporalAntiAliasing`]). /// Doing so greatly reduces SSAO noise. /// /// SSAO is not supported on `WebGL2`, and is not currently supported on `WebGPU` or `DirectX12`. #[derive(Component, ExtractComponent, Reflect, PartialEq, Eq, Hash, Clone, Default, Debug)] -#[reflect(Component)] -pub struct ScreenSpaceAmbientOcclusionSettings { +#[reflect(Component, Debug, Default, Hash, PartialEq)] +#[doc(alias = "Ssao")] +pub struct ScreenSpaceAmbientOcclusion { pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel, } +#[deprecated(since = "0.15.0", note = "Renamed to `ScreenSpaceAmbientOcclusion`")] +pub type ScreenSpaceAmbientOcclusionSettings = ScreenSpaceAmbientOcclusion; + #[derive(Reflect, PartialEq, Eq, Hash, Clone, Copy, Default, Debug)] pub enum ScreenSpaceAmbientOcclusionQualityLevel { Low, @@ -444,7 +449,7 @@ impl FromWorld for SsaoPipelines { #[derive(PartialEq, Eq, Hash, Clone)] struct SsaoPipelineKey { - ssao_settings: ScreenSpaceAmbientOcclusionSettings, + ssao_settings: ScreenSpaceAmbientOcclusion, temporal_jitter: bool, } @@ -484,7 +489,7 @@ fn extract_ssao_settings( mut commands: Commands, cameras: Extract< Query< - (Entity, &Camera, &ScreenSpaceAmbientOcclusionSettings, &Msaa), + (Entity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa), (With, With, With), >, >, @@ -516,7 +521,7 @@ fn prepare_ssao_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views: Query<(Entity, &ExtractedCamera), With>, + views: Query<(Entity, &ExtractedCamera), With>, ) { for (entity, camera) in &views { let Some(physical_viewport_size) = camera.physical_viewport_size else { @@ -603,11 +608,7 @@ fn prepare_ssao_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, pipeline: Res, - views: Query<( - Entity, - &ScreenSpaceAmbientOcclusionSettings, - Has, - )>, + views: Query<(Entity, &ScreenSpaceAmbientOcclusion, Has)>, ) { for (entity, ssao_settings, temporal_jitter) in &views { let pipeline_id = pipelines.specialize( diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 1ae86b10a1..6d9c8c6dd7 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -60,7 +60,7 @@ pub struct ScreenSpaceReflectionsPlugin; #[derive(Bundle, Default)] pub struct ScreenSpaceReflectionsBundle { /// The component that enables SSR. - pub settings: ScreenSpaceReflectionsSettings, + pub settings: ScreenSpaceReflections, /// The depth prepass, needed for SSR. pub depth_prepass: DepthPrepass, /// The deferred prepass, needed for SSR. @@ -92,7 +92,8 @@ pub struct ScreenSpaceReflectionsBundle { /// which is required for screen-space raymarching. #[derive(Clone, Copy, Component, Reflect)] #[reflect(Component, Default)] -pub struct ScreenSpaceReflectionsSettings { +#[doc(alias = "Ssr")] +pub struct ScreenSpaceReflections { /// The maximum PBR roughness level that will enable screen space /// reflections. pub perceptual_roughness_threshold: f32, @@ -133,10 +134,13 @@ pub struct ScreenSpaceReflectionsSettings { pub use_secant: bool, } -/// A version of [`ScreenSpaceReflectionsSettings`] for upload to the GPU. +#[deprecated(since = "0.15.0", note = "Renamed to `ScreenSpaceReflections`")] +pub type ScreenSpaceReflectionsSettings = ScreenSpaceReflections; + +/// A version of [`ScreenSpaceReflections`] for upload to the GPU. /// /// For more information on these fields, see the corresponding documentation in -/// [`ScreenSpaceReflectionsSettings`]. +/// [`ScreenSpaceReflections`]. #[derive(Clone, Copy, Component, ShaderType)] pub struct ScreenSpaceReflectionsUniform { perceptual_roughness_threshold: f32, @@ -195,8 +199,8 @@ impl Plugin for ScreenSpaceReflectionsPlugin { Shader::from_wgsl ); - app.register_type::() - .add_plugins(ExtractComponentPlugin::::default()); + app.register_type::() + .add_plugins(ExtractComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -234,7 +238,7 @@ impl Plugin for ScreenSpaceReflectionsPlugin { } } -impl Default for ScreenSpaceReflectionsSettings { +impl Default for ScreenSpaceReflections { // Reasonable default values. // // These are from @@ -485,8 +489,8 @@ pub fn prepare_ssr_settings( } } -impl ExtractComponent for ScreenSpaceReflectionsSettings { - type QueryData = Read; +impl ExtractComponent for ScreenSpaceReflections { + type QueryData = Read; type QueryFilter = (); @@ -553,8 +557,8 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { } } -impl From for ScreenSpaceReflectionsUniform { - fn from(settings: ScreenSpaceReflectionsSettings) -> Self { +impl From for ScreenSpaceReflectionsUniform { + fn from(settings: ScreenSpaceReflections) -> Self { Self { perceptual_roughness_threshold: settings.perceptual_roughness_threshold, thickness: settings.thickness, diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 82aedd3d0b..cbf735b592 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -6,9 +6,9 @@ //! for light beams from directional lights to shine through, creating what is //! known as *light shafts* or *god rays*. //! -//! To add volumetric fog to a scene, add [`VolumetricFogSettings`] to the +//! To add volumetric fog to a scene, add [`VolumetricFog`] to the //! camera, and add [`VolumetricLight`] to directional lights that you wish to -//! be volumetric. [`VolumetricFogSettings`] feature numerous settings that +//! be volumetric. [`VolumetricFog`] feature numerous settings that //! allow you to define the accuracy of the simulation, as well as the look of //! the fog. Currently, only interaction with directional lights that have //! shadow maps is supported. Note that the overhead of the effect scales @@ -44,6 +44,7 @@ use bevy_math::{ primitives::{Cuboid, Plane3d}, Vec2, Vec3, }; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ mesh::{Mesh, Meshable}, @@ -71,15 +72,15 @@ pub struct VolumetricFogPlugin; /// /// This allows the light to generate light shafts/god rays. #[derive(Clone, Copy, Component, Default, Debug, Reflect)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct VolumetricLight; /// When placed on a [`bevy_core_pipeline::core_3d::Camera3d`], enables /// volumetric fog and volumetric lighting, also known as light shafts or god /// rays. #[derive(Clone, Copy, Component, Debug, Reflect)] -#[reflect(Component)] -pub struct VolumetricFogSettings { +#[reflect(Component, Default, Debug)] +pub struct VolumetricFog { /// Color of the ambient light. /// /// This is separate from Bevy's [`AmbientLight`](crate::light::AmbientLight) because an @@ -115,6 +116,9 @@ pub struct VolumetricFogSettings { pub step_count: u32, } +#[deprecated(since = "0.15.0", note = "Renamed to `VolumetricFog`")] +pub type VolumetricFogSettings = VolumetricFog; + /// A convenient [`Bundle`] that contains all components necessary to generate a /// fog volume. #[derive(Bundle, Clone, Debug, Default)] @@ -135,7 +139,7 @@ pub struct FogVolumeBundle { } #[derive(Clone, Component, Debug, Reflect)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct FogVolume { /// The color of the fog. /// @@ -218,7 +222,7 @@ impl Plugin for VolumetricFogPlugin { meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into()); meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into()); - app.register_type::() + app.register_type::() .register_type::(); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -261,7 +265,7 @@ impl Plugin for VolumetricFogPlugin { } } -impl Default for VolumetricFogSettings { +impl Default for VolumetricFog { fn default() -> Self { Self { step_count: 64, diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 618e984db0..9a35a92eae 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -47,7 +47,7 @@ use bitflags::bitflags; use crate::{ FogVolume, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, - ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, VolumetricFogSettings, + ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset, VolumetricFog, VolumetricLight, }; @@ -152,7 +152,7 @@ pub struct VolumetricFogPipelineKey { flags: VolumetricFogPipelineKeyFlags, } -/// The same as [`VolumetricFogSettings`] and [`FogVolume`], but formatted for +/// The same as [`VolumetricFog`] and [`FogVolume`], but formatted for /// the GPU. /// /// See the documentation of those structures for more information on these @@ -266,11 +266,11 @@ impl FromWorld for VolumetricFogPipeline { } } -/// Extracts [`VolumetricFogSettings`], [`FogVolume`], and [`VolumetricLight`]s +/// Extracts [`VolumetricFog`], [`FogVolume`], and [`VolumetricLight`]s /// from the main world to the render world. pub fn extract_volumetric_fog( mut commands: Commands, - view_targets: Extract>, + view_targets: Extract>, fog_volumes: Extract>, volumetric_lights: Extract>, ) { @@ -278,10 +278,8 @@ pub fn extract_volumetric_fog( return; } - for (entity, volumetric_fog_settings) in view_targets.iter() { - commands - .get_or_spawn(entity) - .insert(*volumetric_fog_settings); + for (entity, volumetric_fog) in view_targets.iter() { + commands.get_or_spawn(entity).insert(*volumetric_fog); } for (entity, fog_volume, fog_transform) in fog_volumes.iter() { @@ -606,7 +604,7 @@ pub fn prepare_volumetric_fog_pipelines( Has, Has, ), - With, + With, >, meshes: Res>, ) { @@ -666,11 +664,11 @@ pub fn prepare_volumetric_fog_pipelines( } } -/// A system that converts [`VolumetricFogSettings`] into [`VolumetricFogUniform`]s. +/// A system that converts [`VolumetricFog`] into [`VolumetricFogUniform`]s. pub fn prepare_volumetric_fog_uniforms( mut commands: Commands, mut volumetric_lighting_uniform_buffer: ResMut, - view_targets: Query<(Entity, &ExtractedView, &VolumetricFogSettings)>, + view_targets: Query<(Entity, &ExtractedView, &VolumetricFog)>, fog_volumes: Query<(Entity, &FogVolume, &GlobalTransform)>, render_device: Res, render_queue: Res, @@ -690,7 +688,7 @@ pub fn prepare_volumetric_fog_uniforms( local_from_world_matrices.push(fog_transform.compute_matrix().inverse()); } - for (view_entity, extracted_view, volumetric_fog_settings) in view_targets.iter() { + for (view_entity, extracted_view, volumetric_fog) in view_targets.iter() { let world_from_view = extracted_view.world_from_view.compute_matrix(); let mut view_fog_volumes = vec![]; @@ -721,9 +719,9 @@ pub fn prepare_volumetric_fog_uniforms( far_planes: get_far_planes(&view_from_local), fog_color: fog_volume.fog_color.to_linear().to_vec3(), light_tint: fog_volume.light_tint.to_linear().to_vec3(), - ambient_color: volumetric_fog_settings.ambient_color.to_linear().to_vec3(), - ambient_intensity: volumetric_fog_settings.ambient_intensity, - step_count: volumetric_fog_settings.step_count, + ambient_color: volumetric_fog.ambient_color.to_linear().to_vec3(), + ambient_intensity: volumetric_fog.ambient_intensity, + step_count: volumetric_fog.step_count, bounding_radius, absorption: fog_volume.absorption, scattering: fog_volume.scattering, @@ -731,7 +729,7 @@ pub fn prepare_volumetric_fog_uniforms( density_texture_offset: fog_volume.density_texture_offset, scattering_asymmetry: fog_volume.scattering_asymmetry, light_intensity: fog_volume.light_intensity, - jitter_strength: volumetric_fog_settings.jitter, + jitter_strength: volumetric_fog.jitter, }); view_fog_volumes.push(ViewFogVolume { @@ -753,7 +751,7 @@ pub fn prepare_volumetric_fog_uniforms( /// default. pub fn prepare_view_depth_textures_for_volumetric_fog( mut view_targets: Query<&mut Camera3d>, - fog_volumes: Query<&VolumetricFogSettings>, + fog_volumes: Query<&VolumetricFog>, ) { if fog_volumes.is_empty() { return; diff --git a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl index 2c30eddbea..6b4b1791e6 100644 --- a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl +++ b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl @@ -28,7 +28,7 @@ position_view_to_world } -// The GPU version of [`VolumetricFogSettings`]. See the comments in +// The GPU version of [`VolumetricFog`]. See the comments in // `volumetric_fog/mod.rs` for descriptions of the fields here. struct VolumetricFog { clip_from_local: mat4x4, diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index abb24aae7a..0b2c3bf47e 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -56,7 +56,7 @@ impl Plugin for WireframePlugin { /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe; /// Sets the color of the [`Wireframe`] of the entity it is attached to. @@ -69,7 +69,7 @@ pub struct Wireframe; // This could blow up in size if people use random colored wireframes for each mesh. // It will also be important to remove unused materials from the cache. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct WireframeColor { pub color: Color, } @@ -79,11 +79,11 @@ pub struct WireframeColor { /// /// This requires the [`WireframePlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Debug, Default)] pub struct WireframeConfig { /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component. diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index 00293c1d5e..cb6edebbf8 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -58,6 +58,7 @@ use crate::{ /// The documentation for the [`pointer_events`] explains the events this module exposes and /// the order in which they fire. #[derive(Clone, PartialEq, Debug, Reflect, Component)] +#[reflect(Component, Debug)] pub struct Pointer { /// The pointer that triggered this event pub pointer_id: PointerId, diff --git a/crates/bevy_picking/src/focus.rs b/crates/bevy_picking/src/focus.rs index c5fd0b989c..3d10019401 100644 --- a/crates/bevy_picking/src/focus.rs +++ b/crates/bevy_picking/src/focus.rs @@ -190,7 +190,7 @@ fn build_hover_map( /// the entity will be considered pressed. If that entity is instead being hovered by both pointers, /// it will be considered hovered. #[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, PartialEq, Debug)] pub enum PickingInteraction { /// The entity is being pressed down by a pointer. Pressed = 2, diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 29d294913b..4f38d3454d 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -175,7 +175,7 @@ pub mod prelude { /// make an entity non-hoverable, or allow items below it to be hovered. See the documentation on /// the fields for more details. #[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Pickable { /// Should this entity block entities below it from being picked? /// @@ -317,7 +317,7 @@ impl Plugin for DefaultPickingPlugins { /// This plugin contains several settings, and is added to the wrold as a resource after initialization. You /// can configure picking settings at runtime through the resource. #[derive(Copy, Clone, Debug, Resource, Reflect)] -#[reflect(Resource, Default)] +#[reflect(Resource, Default, Debug)] pub struct PickingPlugin { /// Enables and disables all picking features. pub is_enabled: bool, diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index 01c292bc1d..4dbeb46146 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -26,7 +26,7 @@ use crate::backend::HitData; /// This component is needed because pointers can be spawned and despawned, but they need to have a /// stable ID that persists regardless of the Entity they are associated with. #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Component, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, Hash, PartialEq)] pub enum PointerId { /// The mouse pointer. #[default] @@ -65,7 +65,7 @@ impl PointerId { /// Holds a list of entities this pointer is currently interacting with, sorted from nearest to /// farthest. #[derive(Debug, Default, Clone, Component, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct PointerInteraction { pub(crate) sorted_entities: Vec<(Entity, HitData)>, } @@ -93,7 +93,7 @@ pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut /// Tracks the state of the pointer's buttons in response to [`PointerInput`] events. #[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct PointerPress { primary: bool, secondary: bool, @@ -155,7 +155,7 @@ impl PointerButton { /// Component that tracks a pointer's current [`Location`]. #[derive(Debug, Default, Clone, Component, Reflect, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct PointerLocation { /// The [`Location`] of the pointer. Note that a location is both the target, and the position /// on the target. @@ -180,6 +180,7 @@ impl PointerLocation { /// render target. It is up to picking backends to associate a Pointer's `Location` with a /// specific `Camera`, if any. #[derive(Debug, Clone, Component, Reflect, PartialEq)] +#[reflect(Component, Debug, PartialEq)] pub struct Location { /// The [`NormalizedRenderTarget`] associated with the pointer, usually a window. pub target: NormalizedRenderTarget, diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 5b2186c44f..c9511bae16 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -11,8 +11,8 @@ use core::{ cell::UnsafeCell, fmt::{self, Formatter, Pointer}, marker::PhantomData, - mem::{align_of, ManuallyDrop}, - num::NonZero, + mem::ManuallyDrop, + num::NonZeroUsize, ptr::NonNull, }; @@ -535,7 +535,7 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> { /// Creates a dangling pointer with specified alignment. /// See [`NonNull::dangling`]. -pub fn dangling_with_align(align: NonZero) -> NonNull { +pub const fn dangling_with_align(align: NonZeroUsize) -> NonNull { debug_assert!(align.is_power_of_two(), "Alignment must be power of two."); // SAFETY: The pointer will not be null, since it was created // from the address of a `NonZero`. diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 65ded3f8de..c86a92e852 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -10,13 +10,17 @@ keywords = ["bevy"] rust-version = "1.76.0" [features] -default = ["smallvec"] +default = ["smallvec", "debug"] # When enabled, provides Bevy-related reflection implementations bevy = ["smallvec", "smol_str"] glam = ["dep:glam"] petgraph = ["dep:petgraph"] smallvec = ["dep:smallvec"] uuid = ["dep:uuid"] +# Enables features useful for debugging reflection +debug = ["debug_stack"] +# When enabled, keeps track of the current serialization/deserialization context for better error messages +debug_stack = [] # When enabled, allows documentation comments to be accessed via reflection documentation = ["bevy_reflect_derive/documentation"] # Enables function reflection diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs index 92b16288c4..6f90097828 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs @@ -12,8 +12,8 @@ struct NoReflect(f32); fn main() { let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); - //~^ ERROR: `NoReflect` does not provide type registration information - //~| ERROR: `NoReflect` can not provide type information through reflection + //~^ ERROR: `NoReflect` does not implement `GetTypeRegistration` so cannot provide type registration information + //~| ERROR: `NoReflect` does not implement `Typed` so cannot provide static type information // foo doesn't implement Reflect because NoReflect doesn't implement Reflect foo.get_field::("a").unwrap(); diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs index c9f85c0001..0f8ade8e23 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs @@ -25,7 +25,7 @@ mod incorrect_inner_type { //~^ ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected - //~| ERROR: `TheirInner` can not be used as a dynamic type path + //~| ERROR: `TheirInner` does not implement `TypePath` so cannot provide dynamic type path information //~| ERROR: `?` operator has incompatible types struct MyOuter { // Reason: Should not use `MyInner` directly diff --git a/crates/bevy_reflect/src/from_reflect.rs b/crates/bevy_reflect/src/from_reflect.rs index b1ca70f528..7a8c3b289d 100644 --- a/crates/bevy_reflect/src/from_reflect.rs +++ b/crates/bevy_reflect/src/from_reflect.rs @@ -22,8 +22,8 @@ use crate::{FromType, PartialReflect, Reflect}; /// [`DynamicStruct`]: crate::DynamicStruct /// [crate-level documentation]: crate #[diagnostic::on_unimplemented( - message = "`{Self}` can not be created through reflection", - note = "consider annotating `{Self}` with `#[derive(FromReflect)]`" + message = "`{Self}` does not implement `FromReflect` so cannot be created through reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait FromReflect: Reflect + Sized { /// Constructs a concrete instance of `Self` from a reflected value. diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index a924231ff4..770f0fa0c2 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -57,7 +57,7 @@ //! Closures, on the other hand, are special functions that do capture their environment. //! These are always defined with anonymous function syntax. //! -//! ```rust +//! ``` //! // A closure that captures an immutable reference to a variable //! let c = 123; //! let add = |a: i32, b: i32| a + b + c; @@ -94,6 +94,35 @@ //! For other functions that don't conform to one of the above signatures, //! [`DynamicFunction`] and [`DynamicFunctionMut`] can instead be created manually. //! +//! # Function Registration +//! +//! This module also provides a [`FunctionRegistry`] that can be used to register functions and closures +//! by name so that they may be retrieved and called dynamically. +//! +//! ``` +//! # use bevy_reflect::func::{ArgList, FunctionRegistry}; +//! fn add(a: i32, b: i32) -> i32 { +//! a + b +//! } +//! +//! let mut registry = FunctionRegistry::default(); +//! +//! // You can register functions and methods by their `std::any::type_name`: +//! registry.register(add).unwrap(); +//! +//! // Or you can register them by a custom name: +//! registry.register_with_name("mul", |a: i32, b: i32| a * b).unwrap(); +//! +//! // You can then retrieve and call these functions by name: +//! let reflect_add = registry.get(std::any::type_name_of_val(&add)).unwrap(); +//! let value = reflect_add.call(ArgList::default().push_owned(10_i32).push_owned(5_i32)).unwrap(); +//! assert_eq!(value.unwrap_owned().try_downcast_ref::(), Some(&15)); +//! +//! let reflect_mul = registry.get("mul").unwrap(); +//! let value = reflect_mul.call(ArgList::default().push_owned(10_i32).push_owned(5_i32)).unwrap(); +//! assert_eq!(value.unwrap_owned().try_downcast_ref::(), Some(&50)); +//! ``` +//! //! [`PartialReflect`]: crate::PartialReflect //! [`Reflect`]: crate::Reflect //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 06823374b0..8d54a5d5c9 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -1,10 +1,10 @@ use crate as bevy_reflect; -use crate::prelude::ReflectDefault; +use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; use glam::*; impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct IVec2 { x: i32, @@ -12,7 +12,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct IVec3 { x: i32, @@ -21,7 +21,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct IVec4 { x: i32, @@ -32,7 +32,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct I64Vec2 { x: i64, @@ -41,7 +41,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct I64Vec3 { x: i64, @@ -51,7 +51,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct I64Vec4 { x: i64, @@ -62,7 +62,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct UVec2 { x: u32, @@ -70,7 +70,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct UVec3 { x: u32, @@ -79,7 +79,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct UVec4 { x: u32, @@ -90,7 +90,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct U64Vec2 { x: u64, @@ -98,7 +98,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct U64Vec3 { x: u64, @@ -107,7 +107,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct U64Vec4 { x: u64, @@ -118,7 +118,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Vec2 { x: f32, @@ -126,7 +126,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Vec3 { x: f32, @@ -135,7 +135,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Vec3A { x: f32, @@ -144,7 +144,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Vec4 { x: f32, @@ -155,7 +155,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct BVec2 { x: bool, @@ -163,7 +163,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct BVec3 { x: bool, @@ -172,7 +172,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct BVec4 { x: bool, @@ -183,7 +183,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DVec2 { x: f64, @@ -191,7 +191,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DVec3 { x: f64, @@ -200,7 +200,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DVec4 { x: f64, @@ -211,7 +211,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Mat2 { x_axis: Vec2, @@ -219,7 +219,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Mat3 { x_axis: Vec3, @@ -228,7 +228,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Mat3A { x_axis: Vec3A, @@ -237,7 +237,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Mat4 { x_axis: Vec4, @@ -248,7 +248,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DMat2 { x_axis: DVec2, @@ -256,7 +256,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DMat3 { x_axis: DVec3, @@ -265,7 +265,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DMat4 { x_axis: DVec4, @@ -276,7 +276,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Affine2 { matrix2: Mat2, @@ -284,7 +284,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Affine3A { matrix3: Mat3A, @@ -293,7 +293,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DAffine2 { matrix2: DMat2, @@ -301,7 +301,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DAffine3 { matrix3: DMat3, @@ -310,7 +310,7 @@ impl_reflect!( ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct Quat { x: f32, @@ -320,7 +320,7 @@ impl_reflect!( } ); impl_reflect!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, PartialEq, Default, Deserialize, Serialize)] #[type_path = "glam"] struct DQuat { x: f64, @@ -330,6 +330,6 @@ impl_reflect!( } ); -impl_reflect_value!(::glam::EulerRot(Debug, Default)); -impl_reflect_value!(::glam::BVec3A(Debug, Default)); -impl_reflect_value!(::glam::BVec4A(Debug, Default)); +impl_reflect_value!(::glam::EulerRot(Debug, Default, Deserialize, Serialize)); +impl_reflect_value!(::glam::BVec3A(Debug, Default, Deserialize, Serialize)); +impl_reflect_value!(::glam::BVec4A(Debug, Default, Deserialize, Serialize)); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index c9f27a4cb3..85f63be654 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1029,7 +1029,6 @@ macro_rules! impl_reflect_for_hashset { } impl_type_path!(::bevy_utils::NoOpHash); -impl_type_path!(::bevy_utils::EntityHash); impl_type_path!(::bevy_utils::FixedState); impl_reflect_for_hashset!(::std::collections::HashSet); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 64216bfc09..8685fb07d8 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -473,7 +473,7 @@ //! //! | Default | Dependencies | //! | :-----: | :---------------------------------------: | -//! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] | +//! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] | //! //! This feature makes it so that the appropriate reflection traits are implemented on all the types //! necessary for the [Bevy] game engine. @@ -493,6 +493,18 @@ //! This can be useful for generating documentation for scripting language interop or //! for displaying tooltips in an editor. //! +//! ## `debug` +//! +//! | Default | Dependencies | +//! | :-----: | :-------------------------------------------: | +//! | ✅ | `debug_stack` | +//! +//! This feature enables useful debug features for reflection. +//! +//! This includes the `debug_stack` feature, +//! which enables capturing the type stack when serializing or deserializing a type +//! and displaying it in error messages. +//! //! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming //! [Bevy]: https://bevyengine.org/ //! [limitations]: #limitations @@ -534,6 +546,7 @@ mod list; mod map; mod path; mod reflect; +mod reflectable; mod remote; mod set; mod struct_trait; @@ -562,6 +575,8 @@ pub mod attributes; mod enums; pub mod serde; pub mod std_traits; +#[cfg(feature = "debug_stack")] +mod type_info_stack; pub mod utility; /// The reflect prelude. @@ -588,6 +603,7 @@ pub use list::*; pub use map::*; pub use path::*; pub use reflect::*; +pub use reflectable::*; pub use remote::*; pub use set::*; pub use struct_trait::*; @@ -620,6 +636,10 @@ pub mod __macro_exports { /// /// This trait has a blanket implementation for all types that implement `GetTypeRegistration` /// and manual implementations for all dynamic types (which simply do nothing). + #[diagnostic::on_unimplemented( + message = "`{Self}` does not implement `GetTypeRegistration` so cannot be registered for reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" + )] pub trait RegisterForReflection { #[allow(unused_variables)] fn __register(registry: &mut TypeRegistry) {} @@ -1614,7 +1634,7 @@ mod tests { // TypeInfo (instance) let value: &dyn Reflect = &123_i32; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Struct @@ -1635,7 +1655,7 @@ mod tests { assert_eq!(usize::type_path(), info.field_at(1).unwrap().type_path()); let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Struct (generic) @@ -1657,7 +1677,7 @@ mod tests { foo: String::from("Hello!"), bar: 321, }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::>()); // Struct (dynamic field) @@ -1687,7 +1707,7 @@ mod tests { foo: DynamicStruct::default(), bar: 321, }; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Tuple Struct @@ -1713,7 +1733,7 @@ mod tests { assert!(info.field_at(1).unwrap().type_info().unwrap().is::()); let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!")); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // List @@ -1728,7 +1748,7 @@ mod tests { assert_eq!(usize::type_path(), info.item_ty().path()); let value: &dyn Reflect = &vec![123_usize]; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // List (SmallVec) @@ -1745,7 +1765,7 @@ mod tests { let value: MySmallVec = smallvec::smallvec![String::default(); 2]; let value: &dyn Reflect = &value; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); } @@ -1761,7 +1781,7 @@ mod tests { assert_eq!(3, info.capacity()); let value: &dyn Reflect = &[1usize, 2usize, 3usize]; - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Cow<'static, str> @@ -1773,7 +1793,7 @@ mod tests { assert_eq!(std::any::type_name::(), info.type_path()); let value: &dyn Reflect = &Cow::<'static, str>::Owned("Hello!".to_string()); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Cow<'static, [u8]> @@ -1788,7 +1808,7 @@ mod tests { assert_eq!(std::any::type_name::(), info.item_ty().path()); let value: &dyn Reflect = &Cow::<'static, [u8]>::Owned(vec![0, 1, 2, 3]); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Map @@ -1806,7 +1826,7 @@ mod tests { assert_eq!(f32::type_path(), info.value_ty().path()); let value: &dyn Reflect = &MyMap::new(); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); // Value @@ -1818,7 +1838,7 @@ mod tests { assert_eq!(MyValue::type_path(), info.type_path()); let value: &dyn Reflect = &String::from("Hello!"); - let info = value.get_represented_type_info().unwrap(); + let info = value.reflect_type_info(); assert!(info.is::()); } @@ -2346,9 +2366,7 @@ bevy_reflect::tests::Test { fn short_type_path() -> &'static str { static CELL: GenericTypePathCell = GenericTypePathCell::new(); - CELL.get_or_insert::(|| { - bevy_utils::get_short_name(std::any::type_name::()) - }) + CELL.get_or_insert::(|| bevy_utils::ShortName::of::().to_string()) } fn type_ident() -> Option<&'static str> { @@ -2738,7 +2756,7 @@ bevy_reflect::tests::Test { } #[reflect_remote(external_crate::TheirOuter)] - struct MyOuter { + struct MyOuter { #[reflect(remote = MyInner)] pub a: external_crate::TheirInner, #[reflect(remote = MyInner)] @@ -2786,7 +2804,7 @@ bevy_reflect::tests::Test { #[reflect_remote(external_crate::TheirOuter)] #[derive(Debug)] - enum MyOuter { + enum MyOuter { Unit, Tuple(#[reflect(remote = MyInner)] external_crate::TheirInner), Struct { @@ -2899,7 +2917,7 @@ bevy_reflect::tests::Test { } #[reflect_remote(external_crate::TheirOuter)] - struct MyOuter { + struct MyOuter { #[reflect(remote = MyInner)] pub inner: external_crate::TheirInner, } @@ -2943,12 +2961,7 @@ bevy_reflect::tests::Test { let output = to_string_pretty(&ser, config).unwrap(); let expected = r#" { - "glam::Quat": ( - x: 1.0, - y: 2.0, - z: 3.0, - w: 4.0, - ), + "glam::Quat": (1.0, 2.0, 3.0, 4.0), }"#; assert_eq!(expected, format!("\n{output}")); @@ -2958,12 +2971,7 @@ bevy_reflect::tests::Test { fn quat_deserialization() { let data = r#" { - "glam::Quat": ( - x: 1.0, - y: 2.0, - z: 3.0, - w: 4.0, - ), + "glam::Quat": (1.0, 2.0, 3.0, 4.0), }"#; let mut registry = TypeRegistry::default(); @@ -3002,11 +3010,7 @@ bevy_reflect::tests::Test { let output = to_string_pretty(&ser, config).unwrap(); let expected = r#" { - "glam::Vec3": ( - x: 12.0, - y: 3.0, - z: -6.9, - ), + "glam::Vec3": (12.0, 3.0, -6.9), }"#; assert_eq!(expected, format!("\n{output}")); @@ -3016,11 +3020,7 @@ bevy_reflect::tests::Test { fn vec3_deserialization() { let data = r#" { - "glam::Vec3": ( - x: 12.0, - y: 3.0, - z: -6.9, - ), + "glam::Vec3": (12.0, 3.0, -6.9), }"#; let mut registry = TypeRegistry::default(); diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index c3d8ccfa2d..afc8c5c28b 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -237,7 +237,7 @@ impl<'a> ReflectPath<'a> for &'a str { /// [`Array`]: crate::Array /// [`Enum`]: crate::Enum #[diagnostic::on_unimplemented( - message = "`{Self}` does not provide a reflection path", + message = "`{Self}` does not implement `GetPath` so cannot be accessed by reflection path", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait GetPath: PartialReflect { @@ -478,6 +478,13 @@ impl From<[Access<'static>; N]> for ParsedPath { } } +impl<'a> TryFrom<&'a str> for ParsedPath { + type Error = ReflectPathError<'a>; + fn try_from(value: &'a str) -> Result { + ParsedPath::parse(value) + } +} + impl fmt::Display for ParsedPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for OffsetAccess { access, .. } in &self.0 { @@ -585,6 +592,21 @@ mod tests { }) } + #[test] + fn try_from() { + assert_eq!( + ParsedPath::try_from("w").unwrap().0, + &[offset(access_field("w"), 1)] + ); + + let r = ParsedPath::try_from("w["); + let matches = matches!(r, Err(ReflectPathError::ParseError { .. })); + assert!( + matches, + "ParsedPath::try_from did not return a ParseError for \"w[\"" + ); + } + #[test] fn parsed_path_parse() { assert_eq!( diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 0456d209c7..6538d55c29 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,7 +1,7 @@ use crate::{ array_debug, enum_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, DynamicTypePath, Enum, List, Map, Set, Struct, Tuple, TupleStruct, - TypeInfo, TypePath, Typed, ValueInfo, + tuple_struct_debug, Array, DynamicTypePath, DynamicTyped, Enum, List, Map, Set, Struct, Tuple, + TupleStruct, TypeInfo, TypePath, Typed, ValueInfo, }; use std::{ any::{Any, TypeId}, @@ -142,7 +142,7 @@ pub enum ApplyError { }, } -/// A zero-sized enumuration of the "kinds" of a reflected type. +/// A zero-sized enumeration of the "kinds" of a reflected type. /// /// A [`ReflectKind`] is obtained via [`PartialReflect::reflect_kind`], /// or via [`ReflectRef::kind`],[`ReflectMut::kind`] or [`ReflectOwned::kind`]. @@ -399,16 +399,20 @@ where /// Doing so will automatically implement this trait, [`PartialReflect`], and many other useful traits for reflection, /// including one of the appropriate subtraits: [`Struct`], [`TupleStruct`] or [`Enum`]. /// +/// If you need to use this trait as a generic bound along with other reflection traits, +/// for your convenience, consider using [`Reflectable`] instead. +/// /// See the [crate-level documentation] to see how this trait can be used. /// /// [`bevy_reflect`]: crate /// [the derive macro]: bevy_reflect_derive::Reflect +/// [`Reflectable`]: crate::Reflectable /// [crate-level documentation]: crate #[diagnostic::on_unimplemented( message = "`{Self}` does not implement `Reflect` so cannot be fully reflected", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] -pub trait Reflect: PartialReflect + Any { +pub trait Reflect: PartialReflect + DynamicTyped + Any { /// Returns the value as a [`Box`][std::any::Any]. /// /// For remote wrapper types, this will return the remote type instead. diff --git a/crates/bevy_reflect/src/reflectable.rs b/crates/bevy_reflect/src/reflectable.rs new file mode 100644 index 0000000000..08226bc214 --- /dev/null +++ b/crates/bevy_reflect/src/reflectable.rs @@ -0,0 +1,33 @@ +use crate::{GetTypeRegistration, Reflect, TypePath, Typed}; + +/// A catch-all trait that is bound by the core reflection traits, +/// useful to simplify reflection-based generic type bounds. +/// +/// You do _not_ need to implement this trait manually. +/// It is automatically implemented for all types that implement its supertraits. +/// And these supertraits are all automatically derived with the [`Reflect` derive macro]. +/// +/// This should namely be used to bound generic arguments to the necessary traits for reflection. +/// Doing this has the added benefit of reducing migration costs, as a change to the required traits +/// is automatically handled by this trait. +/// +/// For now, the supertraits of this trait includes: +/// * [`Reflect`] +/// * [`GetTypeRegistration`] +/// * [`Typed`] +/// * [`TypePath`] +/// +/// ## Example +/// +/// ``` +/// # use bevy_reflect::{Reflect, Reflectable}; +/// #[derive(Reflect)] +/// struct MyStruct { +/// value: T +/// } +/// ``` +/// +/// [`Reflect` derive macro]: bevy_reflect_derive::Reflect +pub trait Reflectable: Reflect + GetTypeRegistration + Typed + TypePath {} + +impl Reflectable for T {} diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs deleted file mode 100644 index 5563d9192f..0000000000 --- a/crates/bevy_reflect/src/serde/de.rs +++ /dev/null @@ -1,1712 +0,0 @@ -use crate::serde::SerializationData; -use crate::{ - ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicSet, DynamicStruct, - DynamicTuple, DynamicTupleStruct, DynamicVariant, EnumInfo, ListInfo, Map, MapInfo, NamedField, - PartialReflect, Reflect, ReflectDeserialize, Set, SetInfo, StructInfo, StructVariantInfo, - TupleInfo, TupleStructInfo, TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, - VariantInfo, -}; -use erased_serde::Deserializer; -use serde::de::{ - DeserializeSeed, EnumAccess, Error, IgnoredAny, MapAccess, SeqAccess, VariantAccess, Visitor, -}; -use serde::Deserialize; -use std::any::TypeId; -use std::fmt; -use std::fmt::{Debug, Display, Formatter}; -use std::slice::Iter; - -pub trait DeserializeValue { - fn deserialize( - deserializer: &mut dyn Deserializer, - type_registry: &TypeRegistry, - ) -> Result, erased_serde::Error>; -} - -trait StructLikeInfo { - fn get_field(&self, name: &str) -> Option<&NamedField>; - fn field_at(&self, index: usize) -> Option<&NamedField>; - fn get_field_len(&self) -> usize; - fn iter_fields(&self) -> Iter<'_, NamedField>; -} - -trait TupleLikeInfo { - fn get_field_len(&self) -> usize; -} - -trait Container { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E>; -} - -impl StructLikeInfo for StructInfo { - fn get_field(&self, name: &str) -> Option<&NamedField> { - self.field(name) - } - - fn field_at(&self, index: usize) -> Option<&NamedField> { - self.field_at(index) - } - - fn get_field_len(&self) -> usize { - self.field_len() - } - - fn iter_fields(&self) -> Iter<'_, NamedField> { - self.iter() - } -} - -impl Container for StructInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on struct {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl StructLikeInfo for StructVariantInfo { - fn get_field(&self, name: &str) -> Option<&NamedField> { - self.field(name) - } - - fn field_at(&self, index: usize) -> Option<&NamedField> { - self.field_at(index) - } - - fn get_field_len(&self) -> usize { - self.field_len() - } - - fn iter_fields(&self) -> Iter<'_, NamedField> { - self.iter() - } -} - -impl Container for StructVariantInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on variant {}", - index, - self.name(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleStructInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleStructInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple struct {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleVariantInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleVariantInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple variant {}", - index, - self.name(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -/// A debug struct used for error messages that displays a list of expected values. -/// -/// # Example -/// -/// ```ignore (Can't import private struct from doctest) -/// let expected = vec!["foo", "bar", "baz"]; -/// assert_eq!("`foo`, `bar`, `baz`", format!("{}", ExpectedValues(expected))); -/// ``` -struct ExpectedValues(Vec); - -impl Debug for ExpectedValues { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let len = self.0.len(); - for (index, item) in self.0.iter().enumerate() { - write!(f, "`{item}`")?; - if index < len - 1 { - write!(f, ", ")?; - } - } - Ok(()) - } -} - -/// Represents a simple reflected identifier. -#[derive(Debug, Clone, Eq, PartialEq)] -struct Ident(String); - -impl<'de> Deserialize<'de> for Ident { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct IdentVisitor; - - impl<'de> Visitor<'de> for IdentVisitor { - type Value = Ident; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("identifier") - } - - fn visit_str(self, value: &str) -> Result - where - E: Error, - { - Ok(Ident(value.to_string())) - } - - fn visit_string(self, value: String) -> Result - where - E: Error, - { - Ok(Ident(value)) - } - } - - deserializer.deserialize_identifier(IdentVisitor) - } -} - -/// A deserializer for type registrations. -/// -/// This will return a [`&TypeRegistration`] corresponding to the given type. -/// This deserializer expects a string containing the _full_ [type path] of the -/// type to find the `TypeRegistration` of. -/// -/// [`&TypeRegistration`]: TypeRegistration -/// [type path]: crate::TypePath::type_path -pub struct TypeRegistrationDeserializer<'a> { - registry: &'a TypeRegistry, -} - -impl<'a> TypeRegistrationDeserializer<'a> { - pub fn new(registry: &'a TypeRegistry) -> Self { - Self { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { - type Value = &'a TypeRegistration; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct TypeRegistrationVisitor<'a>(&'a TypeRegistry); - - impl<'de, 'a> Visitor<'de> for TypeRegistrationVisitor<'a> { - type Value = &'a TypeRegistration; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("string containing `type` entry for the reflected value") - } - - fn visit_str(self, type_path: &str) -> Result - where - E: Error, - { - self.0.get_with_type_path(type_path).ok_or_else(|| { - Error::custom(format_args!("No registration found for `{type_path}`")) - }) - } - } - - deserializer.deserialize_str(TypeRegistrationVisitor(self.registry)) - } -} - -/// A general purpose deserializer for reflected types. -/// -/// This is the deserializer counterpart to [`ReflectSerializer`]. -/// -/// See [`TypedReflectDeserializer`] for a deserializer that expects a known type. -/// -/// # Input -/// -/// This deserializer expects a map with a single entry, -/// where the key is the _full_ [type path] of the reflected type -/// and the value is the serialized data. -/// -/// # Output -/// -/// This deserializer will return a [`Box`] containing the deserialized data. -/// -/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, -/// this `Box` will contain the expected type. -/// For example, deserializing an `i32` will return a `Box` (as a `Box`). -/// -/// Otherwise, this `Box` will contain the dynamic equivalent. -/// For example, a deserialized struct might return a [`Box`] -/// and a deserialized `Vec` might return a [`Box`]. -/// -/// This means that if the actual type is needed, these dynamic representations will need to -/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. -/// -/// # Example -/// -/// ``` -/// # use serde::de::DeserializeSeed; -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::ReflectDeserializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = r#"{ -/// "my_crate::MyStruct": ( -/// value: 123 -/// ) -/// }"#; -/// -/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); -/// let reflect_deserializer = ReflectDeserializer::new(®istry); -/// -/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); -/// -/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, -/// // we know that its deserialized value will be a `DynamicStruct`, -/// // although it will represent `MyStruct`. -/// assert!(output.as_partial_reflect().represents::()); -/// -/// // We can convert back to `MyStruct` using `FromReflect`. -/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); -/// assert_eq!(value, MyStruct { value: 123 }); -/// -/// // We can also do this dynamically with `ReflectFromReflect`. -/// let type_id = output.get_represented_type_info().unwrap().type_id(); -/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); -/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); -/// assert!(value.is::()); -/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); -/// ``` -/// -/// [`ReflectSerializer`]: crate::serde::ReflectSerializer -/// [type path]: crate::TypePath::type_path -/// [`Box`]: crate::Reflect -/// [`ReflectKind::Value`]: crate::ReflectKind::Value -/// [`ReflectDeserialize`]: crate::ReflectDeserialize -/// [`Box`]: crate::DynamicStruct -/// [`Box`]: crate::DynamicList -/// [`FromReflect`]: crate::FromReflect -/// [`ReflectFromReflect`]: crate::ReflectFromReflect -pub struct ReflectDeserializer<'a> { - registry: &'a TypeRegistry, -} - -impl<'a> ReflectDeserializer<'a> { - pub fn new(registry: &'a TypeRegistry) -> Self { - Self { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for ReflectDeserializer<'a> { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct UntypedReflectDeserializerVisitor<'a> { - registry: &'a TypeRegistry, - } - - impl<'a, 'de> Visitor<'de> for UntypedReflectDeserializerVisitor<'a> { - type Value = Box; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter - .write_str("map containing `type` and `value` entries for the reflected value") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let registration = map - .next_key_seed(TypeRegistrationDeserializer::new(self.registry))? - .ok_or_else(|| Error::invalid_length(0, &"a single entry"))?; - - let value = map.next_value_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })?; - - if map.next_key::()?.is_some() { - return Err(Error::invalid_length(2, &"a single entry")); - } - - Ok(value) - } - } - - deserializer.deserialize_map(UntypedReflectDeserializerVisitor { - registry: self.registry, - }) - } -} - -/// A deserializer for reflected types whose [`TypeRegistration`] is known. -/// -/// This is the deserializer counterpart to [`TypedReflectSerializer`]. -/// -/// See [`ReflectDeserializer`] for a deserializer that expects an unknown type. -/// -/// # Input -/// -/// Since the type is already known, the input is just the serialized data. -/// -/// # Output -/// -/// This deserializer will return a [`Box`] containing the deserialized data. -/// -/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, -/// this `Box` will contain the expected type. -/// For example, deserializing an `i32` will return a `Box` (as a `Box`). -/// -/// Otherwise, this `Box` will contain the dynamic equivalent. -/// For example, a deserialized struct might return a [`Box`] -/// and a deserialized `Vec` might return a [`Box`]. -/// -/// This means that if the actual type is needed, these dynamic representations will need to -/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. -/// -/// # Example -/// -/// ``` -/// # use std::any::TypeId; -/// # use serde::de::DeserializeSeed; -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::TypedReflectDeserializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = r#"( -/// value: 123 -/// )"#; -/// -/// let registration = registry.get(TypeId::of::()).unwrap(); -/// -/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); -/// let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); -/// -/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); -/// -/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, -/// // we know that its deserialized value will be a `DynamicStruct`, -/// // although it will represent `MyStruct`. -/// assert!(output.as_partial_reflect().represents::()); -/// -/// // We can convert back to `MyStruct` using `FromReflect`. -/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); -/// assert_eq!(value, MyStruct { value: 123 }); -/// -/// // We can also do this dynamically with `ReflectFromReflect`. -/// let type_id = output.get_represented_type_info().unwrap().type_id(); -/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); -/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); -/// assert!(value.is::()); -/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); -/// ``` -/// -/// [`TypedReflectSerializer`]: crate::serde::TypedReflectSerializer -/// [`Box`]: crate::Reflect -/// [`ReflectKind::Value`]: crate::ReflectKind::Value -/// [`ReflectDeserialize`]: crate::ReflectDeserialize -/// [`Box`]: crate::DynamicStruct -/// [`Box`]: crate::DynamicList -/// [`FromReflect`]: crate::FromReflect -/// [`ReflectFromReflect`]: crate::ReflectFromReflect -pub struct TypedReflectDeserializer<'a> { - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a> TypedReflectDeserializer<'a> { - pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { - Self { - registration, - registry, - } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let type_path = self.registration.type_info().type_path(); - - // Handle both Value case and types that have a custom `ReflectDeserialize` - if let Some(deserialize_reflect) = self.registration.data::() { - let value = deserialize_reflect.deserialize(deserializer)?; - return Ok(value.into_partial_reflect()); - } - - match self.registration.type_info() { - TypeInfo::Struct(struct_info) => { - let mut dynamic_struct = deserializer.deserialize_struct( - struct_info.type_path_table().ident().unwrap(), - struct_info.field_names(), - StructVisitor { - struct_info, - registration: self.registration, - registry: self.registry, - }, - )?; - dynamic_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_struct)) - } - TypeInfo::TupleStruct(tuple_struct_info) => { - let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( - tuple_struct_info.type_path_table().ident().unwrap(), - tuple_struct_info.field_len(), - TupleStructVisitor { - tuple_struct_info, - registry: self.registry, - registration: self.registration, - }, - )?; - dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple_struct)) - } - TypeInfo::List(list_info) => { - let mut dynamic_list = deserializer.deserialize_seq(ListVisitor { - list_info, - registry: self.registry, - })?; - dynamic_list.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_list)) - } - TypeInfo::Array(array_info) => { - let mut dynamic_array = deserializer.deserialize_tuple( - array_info.capacity(), - ArrayVisitor { - array_info, - registry: self.registry, - }, - )?; - dynamic_array.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_array)) - } - TypeInfo::Map(map_info) => { - let mut dynamic_map = deserializer.deserialize_map(MapVisitor { - map_info, - registry: self.registry, - })?; - dynamic_map.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_map)) - } - TypeInfo::Set(set_info) => { - let mut dynamic_set = deserializer.deserialize_seq(SetVisitor { - set_info, - registry: self.registry, - })?; - dynamic_set.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_set)) - } - TypeInfo::Tuple(tuple_info) => { - let mut dynamic_tuple = deserializer.deserialize_tuple( - tuple_info.field_len(), - TupleVisitor { - tuple_info, - registration: self.registration, - registry: self.registry, - }, - )?; - dynamic_tuple.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple)) - } - TypeInfo::Enum(enum_info) => { - let mut dynamic_enum = if enum_info.type_path_table().module_path() - == Some("core::option") - && enum_info.type_path_table().ident() == Some("Option") - { - deserializer.deserialize_option(OptionVisitor { - enum_info, - registry: self.registry, - })? - } else { - deserializer.deserialize_enum( - enum_info.type_path_table().ident().unwrap(), - enum_info.variant_names(), - EnumVisitor { - enum_info, - registration: self.registration, - registry: self.registry, - }, - )? - }; - dynamic_enum.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_enum)) - } - TypeInfo::Value(_) => { - // This case should already be handled - Err(Error::custom(format_args!( - "Type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", - ))) - } - } - } -} - -struct StructVisitor<'a> { - struct_info: &'static StructInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { - type Value = DynamicStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected struct value") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - visit_struct(&mut map, self.struct_info, self.registration, self.registry) - } -} - -struct TupleStructVisitor<'a> { - tuple_struct_info: &'static TupleStructInfo, - registry: &'a TypeRegistry, - registration: &'a TypeRegistration, -} - -impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { - type Value = DynamicTupleStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple struct value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple( - &mut seq, - self.tuple_struct_info, - self.registration, - self.registry, - ) - .map(DynamicTupleStruct::from) - } -} - -struct TupleVisitor<'a> { - tuple_info: &'static TupleInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> { - type Value = DynamicTuple; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) - } -} - -struct ArrayVisitor<'a> { - array_info: &'static ArrayInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> { - type Value = DynamicArray; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected array value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); - let registration = get_registration( - self.array_info.item_ty().id(), - self.array_info.item_ty().path(), - self.registry, - )?; - while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })? { - vec.push(value); - } - - if vec.len() != self.array_info.capacity() { - return Err(Error::invalid_length( - vec.len(), - &self.array_info.capacity().to_string().as_str(), - )); - } - - Ok(DynamicArray::new(vec.into_boxed_slice())) - } -} - -struct ListVisitor<'a> { - list_info: &'static ListInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { - type Value = DynamicList; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected list value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let mut list = DynamicList::default(); - let registration = get_registration( - self.list_info.item_ty().id(), - self.list_info.item_ty().path(), - self.registry, - )?; - while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })? { - list.push_box(value); - } - Ok(list) - } -} - -struct MapVisitor<'a> { - map_info: &'static MapInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { - type Value = DynamicMap; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected map value") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut dynamic_map = DynamicMap::default(); - let key_registration = get_registration( - self.map_info.key_ty().id(), - self.map_info.key_ty().path(), - self.registry, - )?; - let value_registration = get_registration( - self.map_info.value_ty().id(), - self.map_info.value_ty().path(), - self.registry, - )?; - while let Some(key) = map.next_key_seed(TypedReflectDeserializer { - registration: key_registration, - registry: self.registry, - })? { - let value = map.next_value_seed(TypedReflectDeserializer { - registration: value_registration, - registry: self.registry, - })?; - dynamic_map.insert_boxed(key, value); - } - - Ok(dynamic_map) - } -} - -struct SetVisitor<'a> { - set_info: &'static SetInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { - type Value = DynamicSet; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected set value") - } - - fn visit_seq(self, mut set: V) -> Result - where - V: SeqAccess<'de>, - { - let mut dynamic_set = DynamicSet::default(); - let value_registration = get_registration( - self.set_info.value_ty().id(), - self.set_info.value_ty().path(), - self.registry, - )?; - while let Some(value) = set.next_element_seed(TypedReflectDeserializer { - registration: value_registration, - registry: self.registry, - })? { - dynamic_set.insert_boxed(value); - } - - Ok(dynamic_set) - } -} - -struct EnumVisitor<'a> { - enum_info: &'static EnumInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { - type Value = DynamicEnum; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected enum value") - } - - fn visit_enum(self, data: A) -> Result - where - A: EnumAccess<'de>, - { - let mut dynamic_enum = DynamicEnum::default(); - let (variant_info, variant) = data.variant_seed(VariantDeserializer { - enum_info: self.enum_info, - })?; - - let value: DynamicVariant = match variant_info { - VariantInfo::Unit(..) => variant.unit_variant()?.into(), - VariantInfo::Struct(struct_info) => variant - .struct_variant( - struct_info.field_names(), - StructVariantVisitor { - struct_info, - registration: self.registration, - registry: self.registry, - }, - )? - .into(), - VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { - let registration = tuple_info.get_field_registration(0, self.registry)?; - let value = variant.newtype_variant_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })?; - let mut dynamic_tuple = DynamicTuple::default(); - dynamic_tuple.insert_boxed(value); - dynamic_tuple.into() - } - VariantInfo::Tuple(tuple_info) => variant - .tuple_variant( - tuple_info.field_len(), - TupleVariantVisitor { - tuple_info, - registration: self.registration, - registry: self.registry, - }, - )? - .into(), - }; - let variant_name = variant_info.name(); - let variant_index = self - .enum_info - .index_of(variant_name) - .expect("variant should exist"); - dynamic_enum.set_variant_with_index(variant_index, variant_name, value); - Ok(dynamic_enum) - } -} - -struct VariantDeserializer { - enum_info: &'static EnumInfo, -} - -impl<'de> DeserializeSeed<'de> for VariantDeserializer { - type Value = &'static VariantInfo; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct VariantVisitor(&'static EnumInfo); - - impl<'de> Visitor<'de> for VariantVisitor { - type Value = &'static VariantInfo; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("expected either a variant index or variant name") - } - - fn visit_u32(self, variant_index: u32) -> Result - where - E: Error, - { - self.0.variant_at(variant_index as usize).ok_or_else(|| { - Error::custom(format_args!( - "no variant found at index `{}` on enum `{}`", - variant_index, - self.0.type_path() - )) - }) - } - - fn visit_str(self, variant_name: &str) -> Result - where - E: Error, - { - self.0.variant(variant_name).ok_or_else(|| { - let names = self.0.iter().map(VariantInfo::name); - Error::custom(format_args!( - "unknown variant `{}`, expected one of {:?}", - variant_name, - ExpectedValues(names.collect()) - )) - }) - } - } - - deserializer.deserialize_identifier(VariantVisitor(self.enum_info)) - } -} - -struct StructVariantVisitor<'a> { - struct_info: &'static StructVariantInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { - type Value = DynamicStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected struct variant value") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - visit_struct(&mut map, self.struct_info, self.registration, self.registry) - } -} - -struct TupleVariantVisitor<'a> { - tuple_info: &'static TupleVariantInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> { - type Value = DynamicTuple; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple variant value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) - } -} - -struct OptionVisitor<'a> { - enum_info: &'static EnumInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> { - type Value = DynamicEnum; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected option value of type ")?; - formatter.write_str(self.enum_info.type_path()) - } - - fn visit_none(self) -> Result - where - E: Error, - { - let mut option = DynamicEnum::default(); - option.set_variant("None", ()); - Ok(option) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let variant_info = self.enum_info.variant("Some").unwrap(); - match variant_info { - VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { - let field = tuple_info.field_at(0).unwrap(); - let registration = - get_registration(field.type_id(), field.type_path(), self.registry)?; - let de = TypedReflectDeserializer { - registration, - registry: self.registry, - }; - let mut value = DynamicTuple::default(); - value.insert_boxed(de.deserialize(deserializer)?); - let mut option = DynamicEnum::default(); - option.set_variant("Some", value); - Ok(option) - } - info => Err(Error::custom(format_args!( - "invalid variant, expected `Some` but got `{}`", - info.name() - ))), - } - } -} - -fn visit_struct<'de, T, V>( - map: &mut V, - info: &'static T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: StructLikeInfo, - V: MapAccess<'de>, -{ - let mut dynamic_struct = DynamicStruct::default(); - while let Some(Ident(key)) = map.next_key::()? { - let field = info.get_field(&key).ok_or_else(|| { - let fields = info.iter_fields().map(NamedField::name); - Error::custom(format_args!( - "unknown field `{}`, expected one of {:?}", - key, - ExpectedValues(fields.collect()) - )) - })?; - let registration = get_registration(field.type_id(), field.type_path(), registry)?; - let value = map.next_value_seed(TypedReflectDeserializer { - registration, - registry, - })?; - dynamic_struct.insert_boxed(&key, value); - } - - if let Some(serialization_data) = registration.data::() { - for (skipped_index, skipped_field) in serialization_data.iter_skipped() { - let Some(field) = info.field_at(*skipped_index) else { - continue; - }; - dynamic_struct.insert_boxed( - field.name(), - skipped_field.generate_default().into_partial_reflect(), - ); - } - } - - Ok(dynamic_struct) -} - -fn visit_tuple<'de, T, V>( - seq: &mut V, - info: &T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: TupleLikeInfo + Container, - V: SeqAccess<'de>, -{ - let mut tuple = DynamicTuple::default(); - - let len = info.get_field_len(); - - if len == 0 { - // Handle empty tuple/tuple struct - return Ok(tuple); - } - - let serialization_data = registration.data::(); - - for index in 0..len { - if let Some(value) = serialization_data.and_then(|data| data.generate_default(index)) { - tuple.insert_boxed(value.into_partial_reflect()); - continue; - } - - let value = seq - .next_element_seed(TypedReflectDeserializer { - registration: info.get_field_registration(index, registry)?, - registry, - })? - .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; - tuple.insert_boxed(value); - } - - Ok(tuple) -} - -fn visit_struct_seq<'de, T, V>( - seq: &mut V, - info: &T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: StructLikeInfo + Container, - V: SeqAccess<'de>, -{ - let mut dynamic_struct = DynamicStruct::default(); - - let len = info.get_field_len(); - - if len == 0 { - // Handle unit structs - return Ok(dynamic_struct); - } - - let serialization_data = registration.data::(); - - for index in 0..len { - let name = info.field_at(index).unwrap().name(); - - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or_default() - { - if let Some(value) = serialization_data.unwrap().generate_default(index) { - dynamic_struct.insert_boxed(name, value.into_partial_reflect()); - } - continue; - } - - let value = seq - .next_element_seed(TypedReflectDeserializer { - registration: info.get_field_registration(index, registry)?, - registry, - })? - .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; - dynamic_struct.insert_boxed(name, value); - } - - Ok(dynamic_struct) -} - -fn get_registration<'a, E: Error>( - type_id: TypeId, - type_path: &str, - registry: &'a TypeRegistry, -) -> Result<&'a TypeRegistration, E> { - let registration = registry.get(type_id).ok_or_else(|| { - Error::custom(format_args!("no registration found for type `{type_path}`")) - })?; - Ok(registration) -} - -#[cfg(test)] -mod tests { - use bincode::Options; - use std::any::TypeId; - use std::f32::consts::PI; - use std::ops::RangeInclusive; - - use serde::de::DeserializeSeed; - use serde::Deserialize; - - use bevy_utils::{HashMap, HashSet}; - - use crate as bevy_reflect; - use crate::serde::{ReflectDeserializer, ReflectSerializer, TypedReflectDeserializer}; - use crate::{ - DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistry, - }; - - #[derive(Reflect, Debug, PartialEq)] - struct MyStruct { - primitive_value: i8, - option_value: Option, - option_value_complex: Option, - tuple_value: (f32, usize), - list_value: Vec, - array_value: [i32; 5], - map_value: HashMap, - set_value: HashSet, - struct_value: SomeStruct, - tuple_struct_value: SomeTupleStruct, - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum, - newtype_enum: SomeEnum, - tuple_enum: SomeEnum, - struct_enum: SomeEnum, - ignored_struct: SomeIgnoredStruct, - ignored_tuple_struct: SomeIgnoredTupleStruct, - ignored_struct_variant: SomeIgnoredEnum, - ignored_tuple_variant: SomeIgnoredEnum, - custom_deserialize: CustomDeserialize, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeStruct { - foo: i64, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeTupleStruct(String); - - #[derive(Reflect, Debug, PartialEq)] - struct SomeUnitStruct; - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredStruct { - #[reflect(ignore)] - ignored: i32, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); - - #[derive(Reflect, Debug, PartialEq, Deserialize)] - struct SomeDeserializableStruct { - foo: i64, - } - - /// Implements a custom deserialize using `#[reflect(Deserialize)]`. - /// - /// For testing purposes, this is just the auto-generated one from deriving. - #[derive(Reflect, Debug, PartialEq, Deserialize)] - #[reflect(Deserialize)] - struct CustomDeserialize { - value: usize, - #[serde(alias = "renamed")] - inner_struct: SomeDeserializableStruct, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { foo: String }, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeIgnoredEnum { - Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), - Struct { - #[reflect(ignore)] - foo: String, - }, - } - - fn get_registry() -> TypeRegistry { - let mut registry = TypeRegistry::default(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::<(f32, usize)>(); - registry.register::<[i32; 5]>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register_type_data::, ReflectDeserialize>(); - registry - } - - fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); - map.insert(64, 32); - - let mut set = HashSet::new(); - set.insert(64); - - MyStruct { - primitive_value: 123, - option_value: Some(String::from("Hello world!")), - option_value_complex: Some(SomeStruct { foo: 123 }), - tuple_value: (PI, 1337), - list_value: vec![-2, -1, 0, 1, 2], - array_value: [-2, -1, 0, 1, 2], - map_value: map, - set_value: set, - struct_value: SomeStruct { foo: 999999999 }, - tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum::Unit, - newtype_enum: SomeEnum::NewType(123), - tuple_enum: SomeEnum::Tuple(1.23, 3.21), - struct_enum: SomeEnum::Struct { - foo: String::from("Struct variant value"), - }, - ignored_struct: SomeIgnoredStruct { ignored: 0 }, - ignored_tuple_struct: SomeIgnoredTupleStruct(0), - ignored_struct_variant: SomeIgnoredEnum::Struct { - foo: String::default(), - }, - ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), - custom_deserialize: CustomDeserialize { - value: 100, - inner_struct: SomeDeserializableStruct { foo: 101 }, - }, - } - } - - #[test] - fn should_deserialize() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = r#"{ - "bevy_reflect::serde::de::tests::MyStruct": ( - primitive_value: 123, - option_value: Some("Hello world!"), - option_value_complex: Some(( - foo: 123, - )), - tuple_value: (3.1415927, 1337), - list_value: [ - -2, - -1, - 0, - 1, - 2, - ], - array_value: (-2, -1, 0, 1, 2), - map_value: { - 64: 32, - }, - set_value: [ - 64, - ], - struct_value: ( - foo: 999999999, - ), - tuple_struct_value: ("Tuple Struct"), - unit_struct: (), - unit_enum: Unit, - newtype_enum: NewType(123), - tuple_enum: Tuple(1.23, 3.21), - struct_enum: Struct( - foo: "Struct variant value", - ), - ignored_struct: (), - ignored_tuple_struct: (), - ignored_struct_variant: Struct(), - ignored_tuple_variant: Tuple(), - custom_deserialize: ( - value: 100, - renamed: ( - foo: 101, - ), - ), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_value() { - let input = r#"{ - "f32": 1.23, - }"#; - - let registry = get_registry(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - let output = dynamic_output - .try_take::() - .expect("underlying type should be f32"); - assert_eq!(1.23, output); - } - - #[test] - fn should_deserialized_typed() { - #[derive(Reflect, Debug, PartialEq)] - struct Foo { - bar: i32, - } - - let expected = Foo { bar: 123 }; - - let input = r#"( - bar: 123 - )"#; - - let mut registry = get_registry(); - registry.register::(); - let registration = registry.get(TypeId::of::()).unwrap(); - let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = - ::from_reflect(dynamic_output.as_ref().as_partial_reflect()) - .unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_option() { - #[derive(Reflect, Debug, PartialEq)] - struct OptionTest { - none: Option<()>, - simple: Option, - complex: Option, - } - - let expected = OptionTest { - none: None, - simple: Some(String::from("Hello world!")), - complex: Some(SomeStruct { foo: 123 }), - }; - - let mut registry = get_registry(); - registry.register::(); - registry.register::>(); - - // === Normal === // - let input = r#"{ - "bevy_reflect::serde::de::tests::OptionTest": ( - none: None, - simple: Some("Hello world!"), - complex: Some(( - foo: 123, - )), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output, "failed to deserialize Options"); - - // === Implicit Some === // - let input = r#" - #![enable(implicit_some)] - { - "bevy_reflect::serde::de::tests::OptionTest": ( - none: None, - simple: "Hello world!", - complex: ( - foo: 123, - ), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!( - expected, output, - "failed to deserialize Options with implicit Some" - ); - } - - #[test] - fn enum_should_deserialize() { - #[derive(Reflect)] - enum MyEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { value: String }, - } - - let mut registry = get_registry(); - registry.register::(); - - // === Unit Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Unit, -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Unit); - assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); - - // === NewType Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": NewType(123), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::NewType(123)); - assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); - - // === Tuple Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Tuple(1.23, 3.21), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Tuple(1.23, 3.21)); - assert!(expected - .reflect_partial_eq(output.as_partial_reflect()) - .unwrap()); - - // === Struct Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Struct( - value: "I <3 Enums", - ), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Struct { - value: String::from("I <3 Enums"), - }); - assert!(expected - .reflect_partial_eq(output.as_partial_reflect()) - .unwrap()); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/12462 - #[test] - fn should_reserialize() { - let registry = get_registry(); - let input1 = get_my_struct(); - - let serializer1 = ReflectSerializer::new(&input1, ®istry); - let serialized1 = ron::ser::to_string(&serializer1).unwrap(); - - let mut deserializer = ron::de::Deserializer::from_str(&serialized1).unwrap(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let input2 = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let serializer2 = ReflectSerializer::new(input2.as_partial_reflect(), ®istry); - let serialized2 = ron::ser::to_string(&serializer2).unwrap(); - - assert_eq!(serialized1, serialized2); - } - - #[test] - fn should_deserialize_non_self_describing_binary() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, - 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, - 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, 0, 0, - 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, 0, 0, - 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, - 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, 255, - 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, 0, - 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, 0, - 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, - 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, - 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, - 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, - 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, - ]; - - let deserializer = ReflectDeserializer::new(®istry); - - let dynamic_output = bincode::DefaultOptions::new() - .with_fixint_encoding() - .deserialize_seed(deserializer, &input) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_self_describing_binary() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = vec![ - 129, 217, 40, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, - 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, - 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, - 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, - 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, 145, - 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, - 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, - 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, - 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, - 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, - 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, - ]; - - let mut reader = std::io::BufReader::new(input.as_slice()); - - let deserializer = ReflectDeserializer::new(®istry); - let dynamic_output = deserializer - .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_return_error_if_missing_type_data() { - let mut registry = TypeRegistry::new(); - registry.register::>(); - - let input = r#"{"core::ops::RangeInclusive":(start:0.0,end:1.0)}"#; - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let error = reflect_deserializer - .deserialize(&mut deserializer) - .unwrap_err(); - assert_eq!(error, ron::Error::Message("Type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); - } -} diff --git a/crates/bevy_reflect/src/serde/de/arrays.rs b/crates/bevy_reflect/src/serde/de/arrays.rs new file mode 100644 index 0000000000..2ffd2419a9 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/arrays.rs @@ -0,0 +1,54 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{ArrayInfo, DynamicArray, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{Error, SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Array`] values. +/// +/// [`Array`]: crate::Array +pub(super) struct ArrayVisitor<'a> { + array_info: &'static ArrayInfo, + registry: &'a TypeRegistry, +} + +impl<'a> ArrayVisitor<'a> { + pub fn new(array_info: &'static ArrayInfo, registry: &'a TypeRegistry) -> Self { + Self { + array_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> { + type Value = DynamicArray; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected array value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); + let registration = try_get_registration(self.array_info.item_ty(), self.registry)?; + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal( + registration, + self.registry, + ))? { + vec.push(value); + } + + if vec.len() != self.array_info.capacity() { + return Err(Error::invalid_length( + vec.len(), + &self.array_info.capacity().to_string().as_str(), + )); + } + + Ok(DynamicArray::new(vec.into_boxed_slice())) + } +} diff --git a/crates/bevy_reflect/src/serde/de/deserializer.rs b/crates/bevy_reflect/src/serde/de/deserializer.rs new file mode 100644 index 0000000000..7af7aa0997 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/deserializer.rs @@ -0,0 +1,367 @@ +use crate::serde::de::arrays::ArrayVisitor; +use crate::serde::de::enums::EnumVisitor; +use crate::serde::de::error_utils::make_custom_error; +#[cfg(feature = "debug_stack")] +use crate::serde::de::error_utils::TYPE_INFO_STACK; +use crate::serde::de::lists::ListVisitor; +use crate::serde::de::maps::MapVisitor; +use crate::serde::de::options::OptionVisitor; +use crate::serde::de::sets::SetVisitor; +use crate::serde::de::structs::StructVisitor; +use crate::serde::de::tuple_structs::TupleStructVisitor; +use crate::serde::de::tuples::TupleVisitor; +use crate::serde::TypeRegistrationDeserializer; +use crate::{PartialReflect, ReflectDeserialize, TypeInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, IgnoredAny, MapAccess, Visitor}; +use std::fmt; + +/// A general purpose deserializer for reflected types. +/// +/// This is the deserializer counterpart to [`ReflectSerializer`]. +/// +/// See [`TypedReflectDeserializer`] for a deserializer that expects a known type. +/// +/// # Input +/// +/// This deserializer expects a map with a single entry, +/// where the key is the _full_ [type path] of the reflected type +/// and the value is the serialized data. +/// +/// # Output +/// +/// This deserializer will return a [`Box`] containing the deserialized data. +/// +/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, +/// this `Box` will contain the expected type. +/// For example, deserializing an `i32` will return a `Box` (as a `Box`). +/// +/// Otherwise, this `Box` will contain the dynamic equivalent. +/// For example, a deserialized struct might return a [`Box`] +/// and a deserialized `Vec` might return a [`Box`]. +/// +/// This means that if the actual type is needed, these dynamic representations will need to +/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. +/// +/// # Example +/// +/// ``` +/// # use serde::de::DeserializeSeed; +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::ReflectDeserializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = r#"{ +/// "my_crate::MyStruct": ( +/// value: 123 +/// ) +/// }"#; +/// +/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); +/// let reflect_deserializer = ReflectDeserializer::new(®istry); +/// +/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); +/// +/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, +/// // we know that its deserialized value will be a `DynamicStruct`, +/// // although it will represent `MyStruct`. +/// assert!(output.as_partial_reflect().represents::()); +/// +/// // We can convert back to `MyStruct` using `FromReflect`. +/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); +/// assert_eq!(value, MyStruct { value: 123 }); +/// +/// // We can also do this dynamically with `ReflectFromReflect`. +/// let type_id = output.get_represented_type_info().unwrap().type_id(); +/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); +/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); +/// assert!(value.is::()); +/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); +/// ``` +/// +/// [`ReflectSerializer`]: crate::serde::ReflectSerializer +/// [type path]: crate::TypePath::type_path +/// [`Box`]: crate::Reflect +/// [`ReflectKind::Value`]: crate::ReflectKind::Value +/// [`ReflectDeserialize`]: crate::ReflectDeserialize +/// [`Box`]: crate::DynamicStruct +/// [`Box`]: crate::DynamicList +/// [`FromReflect`]: crate::FromReflect +/// [`ReflectFromReflect`]: crate::ReflectFromReflect +pub struct ReflectDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> ReflectDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + Self { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for ReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct UntypedReflectDeserializerVisitor<'a> { + registry: &'a TypeRegistry, + } + + impl<'a, 'de> Visitor<'de> for UntypedReflectDeserializerVisitor<'a> { + type Value = Box; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter + .write_str("map containing `type` and `value` entries for the reflected value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let registration = map + .next_key_seed(TypeRegistrationDeserializer::new(self.registry))? + .ok_or_else(|| Error::invalid_length(0, &"a single entry"))?; + + let value = map.next_value_seed(TypedReflectDeserializer { + registration, + registry: self.registry, + })?; + + if map.next_key::()?.is_some() { + return Err(Error::invalid_length(2, &"a single entry")); + } + + Ok(value) + } + } + + deserializer.deserialize_map(UntypedReflectDeserializerVisitor { + registry: self.registry, + }) + } +} + +/// A deserializer for reflected types whose [`TypeRegistration`] is known. +/// +/// This is the deserializer counterpart to [`TypedReflectSerializer`]. +/// +/// See [`ReflectDeserializer`] for a deserializer that expects an unknown type. +/// +/// # Input +/// +/// Since the type is already known, the input is just the serialized data. +/// +/// # Output +/// +/// This deserializer will return a [`Box`] containing the deserialized data. +/// +/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, +/// this `Box` will contain the expected type. +/// For example, deserializing an `i32` will return a `Box` (as a `Box`). +/// +/// Otherwise, this `Box` will contain the dynamic equivalent. +/// For example, a deserialized struct might return a [`Box`] +/// and a deserialized `Vec` might return a [`Box`]. +/// +/// This means that if the actual type is needed, these dynamic representations will need to +/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. +/// +/// # Example +/// +/// ``` +/// # use std::any::TypeId; +/// # use serde::de::DeserializeSeed; +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::TypedReflectDeserializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = r#"( +/// value: 123 +/// )"#; +/// +/// let registration = registry.get(TypeId::of::()).unwrap(); +/// +/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); +/// let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); +/// +/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); +/// +/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, +/// // we know that its deserialized value will be a `DynamicStruct`, +/// // although it will represent `MyStruct`. +/// assert!(output.as_partial_reflect().represents::()); +/// +/// // We can convert back to `MyStruct` using `FromReflect`. +/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); +/// assert_eq!(value, MyStruct { value: 123 }); +/// +/// // We can also do this dynamically with `ReflectFromReflect`. +/// let type_id = output.get_represented_type_info().unwrap().type_id(); +/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); +/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); +/// assert!(value.is::()); +/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); +/// ``` +/// +/// [`TypedReflectSerializer`]: crate::serde::TypedReflectSerializer +/// [`Box`]: crate::Reflect +/// [`ReflectKind::Value`]: crate::ReflectKind::Value +/// [`ReflectDeserialize`]: crate::ReflectDeserialize +/// [`Box`]: crate::DynamicStruct +/// [`Box`]: crate::DynamicList +/// [`FromReflect`]: crate::FromReflect +/// [`ReflectFromReflect`]: crate::ReflectFromReflect +pub struct TypedReflectDeserializer<'a> { + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TypedReflectDeserializer<'a> { + pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new()); + + Self { + registration, + registry, + } + } + + /// An internal constructor for creating a deserializer without resetting the type info stack. + pub(super) fn new_internal( + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + registration, + registry, + } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let deserialize_internal = || -> Result { + let type_path = self.registration.type_info().type_path(); + + // Handle both Value case and types that have a custom `ReflectDeserialize` + if let Some(deserialize_reflect) = self.registration.data::() { + let value = deserialize_reflect.deserialize(deserializer)?; + return Ok(value.into_partial_reflect()); + } + + match self.registration.type_info() { + TypeInfo::Struct(struct_info) => { + let mut dynamic_struct = deserializer.deserialize_struct( + struct_info.type_path_table().ident().unwrap(), + struct_info.field_names(), + StructVisitor::new(struct_info, self.registration, self.registry), + )?; + dynamic_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_struct)) + } + TypeInfo::TupleStruct(tuple_struct_info) => { + let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( + tuple_struct_info.type_path_table().ident().unwrap(), + tuple_struct_info.field_len(), + TupleStructVisitor::new( + tuple_struct_info, + self.registration, + self.registry, + ), + )?; + dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple_struct)) + } + TypeInfo::List(list_info) => { + let mut dynamic_list = + deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?; + dynamic_list.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_list)) + } + TypeInfo::Array(array_info) => { + let mut dynamic_array = deserializer.deserialize_tuple( + array_info.capacity(), + ArrayVisitor::new(array_info, self.registry), + )?; + dynamic_array.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_array)) + } + TypeInfo::Map(map_info) => { + let mut dynamic_map = + deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?; + dynamic_map.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_map)) + } + TypeInfo::Set(set_info) => { + let mut dynamic_set = + deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?; + dynamic_set.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_set)) + } + TypeInfo::Tuple(tuple_info) => { + let mut dynamic_tuple = deserializer.deserialize_tuple( + tuple_info.field_len(), + TupleVisitor::new(tuple_info, self.registration, self.registry), + )?; + dynamic_tuple.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple)) + } + TypeInfo::Enum(enum_info) => { + let mut dynamic_enum = if enum_info.type_path_table().module_path() + == Some("core::option") + && enum_info.type_path_table().ident() == Some("Option") + { + deserializer + .deserialize_option(OptionVisitor::new(enum_info, self.registry))? + } else { + deserializer.deserialize_enum( + enum_info.type_path_table().ident().unwrap(), + enum_info.variant_names(), + EnumVisitor::new(enum_info, self.registration, self.registry), + )? + }; + dynamic_enum.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_enum)) + } + TypeInfo::Value(_) => { + // This case should already be handled + Err(make_custom_error(format_args!( + "type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + ))) + } + } + }; + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(self.registration.type_info())); + + let output = deserialize_internal(); + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + + output + } +} diff --git a/crates/bevy_reflect/src/serde/de/enums.rs b/crates/bevy_reflect/src/serde/de/enums.rs new file mode 100644 index 0000000000..8f4a094e83 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/enums.rs @@ -0,0 +1,198 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::serde::de::helpers::ExpectedValues; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; +use crate::serde::de::tuple_utils::{visit_tuple, TupleLikeInfo}; +use crate::serde::TypedReflectDeserializer; +use crate::{ + DynamicEnum, DynamicStruct, DynamicTuple, DynamicVariant, EnumInfo, StructVariantInfo, + TupleVariantInfo, TypeRegistration, TypeRegistry, VariantInfo, +}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Enum`] values. +/// +/// [`Enum`]: crate::Enum +pub(super) struct EnumVisitor<'a> { + enum_info: &'static EnumInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> EnumVisitor<'a> { + pub fn new( + enum_info: &'static EnumInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + enum_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { + type Value = DynamicEnum; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected enum value") + } + + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + let mut dynamic_enum = DynamicEnum::default(); + let (variant_info, variant) = data.variant_seed(VariantDeserializer { + enum_info: self.enum_info, + })?; + + let value: DynamicVariant = match variant_info { + VariantInfo::Unit(..) => variant.unit_variant()?.into(), + VariantInfo::Struct(struct_info) => variant + .struct_variant( + struct_info.field_names(), + StructVariantVisitor { + struct_info, + registration: self.registration, + registry: self.registry, + }, + )? + .into(), + VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { + let registration = try_get_registration( + *TupleLikeInfo::field_at(tuple_info, 0)?.ty(), + self.registry, + )?; + let value = variant.newtype_variant_seed( + TypedReflectDeserializer::new_internal(registration, self.registry), + )?; + let mut dynamic_tuple = DynamicTuple::default(); + dynamic_tuple.insert_boxed(value); + dynamic_tuple.into() + } + VariantInfo::Tuple(tuple_info) => variant + .tuple_variant( + tuple_info.field_len(), + TupleVariantVisitor { + tuple_info, + registration: self.registration, + registry: self.registry, + }, + )? + .into(), + }; + let variant_name = variant_info.name(); + let variant_index = self + .enum_info + .index_of(variant_name) + .expect("variant should exist"); + dynamic_enum.set_variant_with_index(variant_index, variant_name, value); + Ok(dynamic_enum) + } +} + +struct VariantDeserializer { + enum_info: &'static EnumInfo, +} + +impl<'de> DeserializeSeed<'de> for VariantDeserializer { + type Value = &'static VariantInfo; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct VariantVisitor(&'static EnumInfo); + + impl<'de> Visitor<'de> for VariantVisitor { + type Value = &'static VariantInfo; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("expected either a variant index or variant name") + } + + fn visit_u32(self, variant_index: u32) -> Result + where + E: Error, + { + self.0.variant_at(variant_index as usize).ok_or_else(|| { + make_custom_error(format_args!( + "no variant found at index `{}` on enum `{}`", + variant_index, + self.0.type_path() + )) + }) + } + + fn visit_str(self, variant_name: &str) -> Result + where + E: Error, + { + self.0.variant(variant_name).ok_or_else(|| { + let names = self.0.iter().map(VariantInfo::name); + make_custom_error(format_args!( + "unknown variant `{}`, expected one of {:?}", + variant_name, + ExpectedValues::from_iter(names) + )) + }) + } + } + + deserializer.deserialize_identifier(VariantVisitor(self.enum_info)) + } +} + +struct StructVariantVisitor<'a> { + struct_info: &'static StructVariantInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { + type Value = DynamicStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected struct variant value") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + visit_struct(&mut map, self.struct_info, self.registration, self.registry) + } +} + +struct TupleVariantVisitor<'a> { + tuple_info: &'static TupleVariantInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> { + type Value = DynamicTuple; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple variant value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/de/error_utils.rs b/crates/bevy_reflect/src/serde/de/error_utils.rs new file mode 100644 index 0000000000..6a97c2518c --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/error_utils.rs @@ -0,0 +1,26 @@ +use core::fmt::Display; +use serde::de::Error; + +#[cfg(feature = "debug_stack")] +thread_local! { + /// The thread-local [`TypeInfoStack`] used for debugging. + /// + /// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack + pub(super) static TYPE_INFO_STACK: std::cell::RefCell = const { std::cell::RefCell::new( + crate::type_info_stack::TypeInfoStack::new() + ) }; +} + +/// A helper function for generating a custom deserialization error message. +/// +/// This function should be preferred over [`Error::custom`] as it will include +/// other useful information, such as the [type info stack]. +/// +/// [type info stack]: crate::type_info_stack::TypeInfoStack +pub(super) fn make_custom_error(msg: impl Display) -> E { + #[cfg(feature = "debug_stack")] + return TYPE_INFO_STACK + .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + #[cfg(not(feature = "debug_stack"))] + return E::custom(msg); +} diff --git a/crates/bevy_reflect/src/serde/de/helpers.rs b/crates/bevy_reflect/src/serde/de/helpers.rs new file mode 100644 index 0000000000..3243a6c011 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/helpers.rs @@ -0,0 +1,70 @@ +use core::fmt::{Debug, Display, Formatter}; +use serde::de::{Error, Visitor}; +use serde::Deserialize; +use std::fmt; + +/// A debug struct used for error messages that displays a list of expected values. +/// +/// # Example +/// +/// ```ignore (Can't import private struct from doctest) +/// let expected = vec!["foo", "bar", "baz"]; +/// assert_eq!("`foo`, `bar`, `baz`", format!("{}", ExpectedValues(expected))); +/// ``` +pub(super) struct ExpectedValues(pub Vec); + +impl FromIterator for ExpectedValues { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl Debug for ExpectedValues { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let len = self.0.len(); + for (index, item) in self.0.iter().enumerate() { + write!(f, "`{item}`")?; + if index < len - 1 { + write!(f, ", ")?; + } + } + Ok(()) + } +} + +/// Represents a simple reflected identifier. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(super) struct Ident(pub String); + +impl<'de> Deserialize<'de> for Ident { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct IdentVisitor; + + impl<'de> Visitor<'de> for IdentVisitor { + type Value = Ident; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("identifier") + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + Ok(Ident(value.to_string())) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + Ok(Ident(value)) + } + } + + deserializer.deserialize_identifier(IdentVisitor) + } +} diff --git a/crates/bevy_reflect/src/serde/de/lists.rs b/crates/bevy_reflect/src/serde/de/lists.rs new file mode 100644 index 0000000000..d0387f8a0a --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/lists.rs @@ -0,0 +1,46 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicList, ListInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`List`] values. +/// +/// [`List`]: crate::List +pub(super) struct ListVisitor<'a> { + list_info: &'static ListInfo, + registry: &'a TypeRegistry, +} + +impl<'a> ListVisitor<'a> { + pub fn new(list_info: &'static ListInfo, registry: &'a TypeRegistry) -> Self { + Self { + list_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { + type Value = DynamicList; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected list value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut list = DynamicList::default(); + let registration = try_get_registration(self.list_info.item_ty(), self.registry)?; + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal( + registration, + self.registry, + ))? { + list.push_box(value); + } + Ok(list) + } +} diff --git a/crates/bevy_reflect/src/serde/de/maps.rs b/crates/bevy_reflect/src/serde/de/maps.rs new file mode 100644 index 0000000000..1bda2e6837 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/maps.rs @@ -0,0 +1,49 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicMap, Map, MapInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{MapAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Map`] values. +/// +/// [`Map`]: crate::Map +pub(super) struct MapVisitor<'a> { + map_info: &'static MapInfo, + registry: &'a TypeRegistry, +} + +impl<'a> MapVisitor<'a> { + pub fn new(map_info: &'static MapInfo, registry: &'a TypeRegistry) -> Self { + Self { map_info, registry } + } +} + +impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { + type Value = DynamicMap; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected map value") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut dynamic_map = DynamicMap::default(); + let key_registration = try_get_registration(self.map_info.key_ty(), self.registry)?; + let value_registration = try_get_registration(self.map_info.value_ty(), self.registry)?; + while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new_internal( + key_registration, + self.registry, + ))? { + let value = map.next_value_seed(TypedReflectDeserializer::new_internal( + value_registration, + self.registry, + ))?; + dynamic_map.insert_boxed(key, value); + } + + Ok(dynamic_map) + } +} diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs new file mode 100644 index 0000000000..bc92d3ccf8 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -0,0 +1,562 @@ +pub use deserializer::*; +pub use registrations::*; + +mod arrays; +mod deserializer; +mod enums; +mod error_utils; +mod helpers; +mod lists; +mod maps; +mod options; +mod registration_utils; +mod registrations; +mod sets; +mod struct_utils; +mod structs; +mod tuple_structs; +mod tuple_utils; +mod tuples; + +#[cfg(test)] +mod tests { + use bincode::Options; + use std::any::TypeId; + use std::f32::consts::PI; + use std::ops::RangeInclusive; + + use serde::de::DeserializeSeed; + use serde::Deserialize; + + use bevy_utils::{HashMap, HashSet}; + + use crate as bevy_reflect; + use crate::serde::{ReflectDeserializer, ReflectSerializer, TypedReflectDeserializer}; + use crate::{ + DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistry, + }; + + #[derive(Reflect, Debug, PartialEq)] + struct MyStruct { + primitive_value: i8, + option_value: Option, + option_value_complex: Option, + tuple_value: (f32, usize), + list_value: Vec, + array_value: [i32; 5], + map_value: HashMap, + set_value: HashSet, + struct_value: SomeStruct, + tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum, + newtype_enum: SomeEnum, + tuple_enum: SomeEnum, + struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, + custom_deserialize: CustomDeserialize, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeStruct { + foo: i64, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeTupleStruct(String); + + #[derive(Reflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + + #[derive(Reflect, Debug, PartialEq, Deserialize)] + struct SomeDeserializableStruct { + foo: i64, + } + + /// Implements a custom deserialize using `#[reflect(Deserialize)]`. + /// + /// For testing purposes, this is just the auto-generated one from deriving. + #[derive(Reflect, Debug, PartialEq, Deserialize)] + #[reflect(Deserialize)] + struct CustomDeserialize { + value: usize, + #[serde(alias = "renamed")] + inner_struct: SomeDeserializableStruct, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { foo: String }, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + + fn get_registry() -> TypeRegistry { + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::<(f32, usize)>(); + registry.register::<[i32; 5]>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register_type_data::, ReflectDeserialize>(); + registry + } + + fn get_my_struct() -> MyStruct { + let mut map = HashMap::new(); + map.insert(64, 32); + + let mut set = HashSet::new(); + set.insert(64); + + MyStruct { + primitive_value: 123, + option_value: Some(String::from("Hello world!")), + option_value_complex: Some(SomeStruct { foo: 123 }), + tuple_value: (PI, 1337), + list_value: vec![-2, -1, 0, 1, 2], + array_value: [-2, -1, 0, 1, 2], + map_value: map, + set_value: set, + struct_value: SomeStruct { foo: 999999999 }, + tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum::Unit, + newtype_enum: SomeEnum::NewType(123), + tuple_enum: SomeEnum::Tuple(1.23, 3.21), + struct_enum: SomeEnum::Struct { + foo: String::from("Struct variant value"), + }, + ignored_struct: SomeIgnoredStruct { ignored: 0 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(0), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::default(), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), + custom_deserialize: CustomDeserialize { + value: 100, + inner_struct: SomeDeserializableStruct { foo: 101 }, + }, + } + } + + #[test] + fn should_deserialize() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = r#"{ + "bevy_reflect::serde::de::tests::MyStruct": ( + primitive_value: 123, + option_value: Some("Hello world!"), + option_value_complex: Some(( + foo: 123, + )), + tuple_value: (3.1415927, 1337), + list_value: [ + -2, + -1, + 0, + 1, + 2, + ], + array_value: (-2, -1, 0, 1, 2), + map_value: { + 64: 32, + }, + set_value: [ + 64, + ], + struct_value: ( + foo: 999999999, + ), + tuple_struct_value: ("Tuple Struct"), + unit_struct: (), + unit_enum: Unit, + newtype_enum: NewType(123), + tuple_enum: Tuple(1.23, 3.21), + struct_enum: Struct( + foo: "Struct variant value", + ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), + custom_deserialize: ( + value: 100, + renamed: ( + foo: 101, + ), + ), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_value() { + let input = r#"{ + "f32": 1.23, + }"#; + + let registry = get_registry(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + let output = dynamic_output + .try_take::() + .expect("underlying type should be f32"); + assert_eq!(1.23, output); + } + + #[test] + fn should_deserialized_typed() { + #[derive(Reflect, Debug, PartialEq)] + struct Foo { + bar: i32, + } + + let expected = Foo { bar: 123 }; + + let input = r#"( + bar: 123 + )"#; + + let mut registry = get_registry(); + registry.register::(); + let registration = registry.get(TypeId::of::()).unwrap(); + let reflect_deserializer = TypedReflectDeserializer::new_internal(registration, ®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = + ::from_reflect(dynamic_output.as_ref().as_partial_reflect()) + .unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_option() { + #[derive(Reflect, Debug, PartialEq)] + struct OptionTest { + none: Option<()>, + simple: Option, + complex: Option, + } + + let expected = OptionTest { + none: None, + simple: Some(String::from("Hello world!")), + complex: Some(SomeStruct { foo: 123 }), + }; + + let mut registry = get_registry(); + registry.register::(); + registry.register::>(); + + // === Normal === // + let input = r#"{ + "bevy_reflect::serde::de::tests::OptionTest": ( + none: None, + simple: Some("Hello world!"), + complex: Some(( + foo: 123, + )), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output, "failed to deserialize Options"); + + // === Implicit Some === // + let input = r#" + #![enable(implicit_some)] + { + "bevy_reflect::serde::de::tests::OptionTest": ( + none: None, + simple: "Hello world!", + complex: ( + foo: 123, + ), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!( + expected, output, + "failed to deserialize Options with implicit Some" + ); + } + + #[test] + fn enum_should_deserialize() { + #[derive(Reflect)] + enum MyEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { value: String }, + } + + let mut registry = get_registry(); + registry.register::(); + + // === Unit Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Unit, +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Unit); + assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); + + // === NewType Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": NewType(123), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::NewType(123)); + assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); + + // === Tuple Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Tuple(1.23, 3.21), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Tuple(1.23, 3.21)); + assert!(expected + .reflect_partial_eq(output.as_partial_reflect()) + .unwrap()); + + // === Struct Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Struct( + value: "I <3 Enums", + ), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Struct { + value: String::from("I <3 Enums"), + }); + assert!(expected + .reflect_partial_eq(output.as_partial_reflect()) + .unwrap()); + } + + // Regression test for https://github.com/bevyengine/bevy/issues/12462 + #[test] + fn should_reserialize() { + let registry = get_registry(); + let input1 = get_my_struct(); + + let serializer1 = ReflectSerializer::new(&input1, ®istry); + let serialized1 = ron::ser::to_string(&serializer1).unwrap(); + + let mut deserializer = ron::de::Deserializer::from_str(&serialized1).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let input2 = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let serializer2 = ReflectSerializer::new(input2.as_partial_reflect(), ®istry); + let serialized2 = ron::ser::to_string(&serializer2).unwrap(); + + assert_eq!(serialized1, serialized2); + } + + #[test] + fn should_deserialize_non_self_describing_binary() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = vec![ + 1, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, + 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, + 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, 0, 0, + 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, 0, 0, + 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, + 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, 255, + 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, 0, + 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, + 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, + 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, + 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, + 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + ]; + + let deserializer = ReflectDeserializer::new(®istry); + + let dynamic_output = bincode::DefaultOptions::new() + .with_fixint_encoding() + .deserialize_seed(deserializer, &input) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_self_describing_binary() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = vec![ + 129, 217, 40, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, + 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, + 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, + 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, + 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, 145, + 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, + 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, + 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, + 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, + 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, + 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, + ]; + + let mut reader = std::io::BufReader::new(input.as_slice()); + + let deserializer = ReflectDeserializer::new(®istry); + let dynamic_output = deserializer + .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_return_error_if_missing_type_data() { + let mut registry = TypeRegistry::new(); + registry.register::>(); + + let input = r#"{"core::ops::RangeInclusive":(start:0.0,end:1.0)}"#; + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let error = reflect_deserializer + .deserialize(&mut deserializer) + .unwrap_err(); + #[cfg(feature = "debug_stack")] + assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive`)".to_string())); + #[cfg(not(feature = "debug_stack"))] + assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); + } + + #[cfg(feature = "debug_stack")] + mod debug_stack { + use super::*; + + #[test] + fn should_report_context_in_errors() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect)] + struct Bar { + some_other_field: Option, + baz: Baz, + } + + #[derive(Reflect)] + struct Baz { + value: Vec>, + } + + let mut registry = TypeRegistry::new(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::>(); + + let input = r#"{"bevy_reflect::serde::de::tests::debug_stack::Foo":(bar:(some_other_field:Some(123),baz:(value:[(start:0.0,end:1.0)])))}"#; + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let error = reflect_deserializer + .deserialize(&mut deserializer) + .unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_reflect::serde::de::tests::debug_stack::Foo` -> `bevy_reflect::serde::de::tests::debug_stack::Bar` -> `bevy_reflect::serde::de::tests::debug_stack::Baz` -> `alloc::vec::Vec>` -> `core::ops::RangeInclusive`)".to_string() + ) + ); + } + } +} diff --git a/crates/bevy_reflect/src/serde/de/options.rs b/crates/bevy_reflect/src/serde/de/options.rs new file mode 100644 index 0000000000..f4a5467c63 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/options.rs @@ -0,0 +1,63 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicEnum, DynamicTuple, EnumInfo, TypeRegistry, VariantInfo}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Option`] values. +pub(super) struct OptionVisitor<'a> { + enum_info: &'static EnumInfo, + registry: &'a TypeRegistry, +} + +impl<'a> OptionVisitor<'a> { + pub fn new(enum_info: &'static EnumInfo, registry: &'a TypeRegistry) -> Self { + Self { + enum_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> { + type Value = DynamicEnum; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected option value of type ")?; + formatter.write_str(self.enum_info.type_path()) + } + + fn visit_none(self) -> Result + where + E: Error, + { + let mut option = DynamicEnum::default(); + option.set_variant("None", ()); + Ok(option) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let variant_info = self.enum_info.variant("Some").unwrap(); + match variant_info { + VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { + let field = tuple_info.field_at(0).unwrap(); + let registration = try_get_registration(*field.ty(), self.registry)?; + let de = TypedReflectDeserializer::new_internal(registration, self.registry); + let mut value = DynamicTuple::default(); + value.insert_boxed(de.deserialize(deserializer)?); + let mut option = DynamicEnum::default(); + option.set_variant("Some", value); + Ok(option) + } + info => Err(make_custom_error(format_args!( + "invalid variant, expected `Some` but got `{}`", + info.name() + ))), + } + } +} diff --git a/crates/bevy_reflect/src/serde/de/registration_utils.rs b/crates/bevy_reflect/src/serde/de/registration_utils.rs new file mode 100644 index 0000000000..2c88a73939 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/registration_utils.rs @@ -0,0 +1,16 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::{Type, TypeRegistration, TypeRegistry}; +use serde::de::Error; + +/// Attempts to find the [`TypeRegistration`] for a given [type]. +/// +/// [type]: Type +pub(super) fn try_get_registration( + ty: Type, + registry: &TypeRegistry, +) -> Result<&TypeRegistration, E> { + let registration = registry.get(ty.id()).ok_or_else(|| { + make_custom_error(format_args!("no registration found for type `{ty:?}`")) + })?; + Ok(registration) +} diff --git a/crates/bevy_reflect/src/serde/de/registrations.rs b/crates/bevy_reflect/src/serde/de/registrations.rs new file mode 100644 index 0000000000..8b34c348c0 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/registrations.rs @@ -0,0 +1,53 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::{TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, Visitor}; +use std::fmt; + +/// A deserializer for type registrations. +/// +/// This will return a [`&TypeRegistration`] corresponding to the given type. +/// This deserializer expects a string containing the _full_ [type path] of the +/// type to find the `TypeRegistration` of. +/// +/// [`&TypeRegistration`]: TypeRegistration +/// [type path]: crate::TypePath::type_path +pub struct TypeRegistrationDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> TypeRegistrationDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + Self { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { + type Value = &'a TypeRegistration; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct TypeRegistrationVisitor<'a>(&'a TypeRegistry); + + impl<'de, 'a> Visitor<'de> for TypeRegistrationVisitor<'a> { + type Value = &'a TypeRegistration; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("string containing `type` entry for the reflected value") + } + + fn visit_str(self, type_path: &str) -> Result + where + E: Error, + { + self.0.get_with_type_path(type_path).ok_or_else(|| { + make_custom_error(format_args!("no registration found for `{type_path}`")) + }) + } + } + + deserializer.deserialize_str(TypeRegistrationVisitor(self.registry)) + } +} diff --git a/crates/bevy_reflect/src/serde/de/sets.rs b/crates/bevy_reflect/src/serde/de/sets.rs new file mode 100644 index 0000000000..2127f84b48 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/sets.rs @@ -0,0 +1,44 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicSet, Set, SetInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Set`] values. +/// +/// [`Set`]: crate::Set +pub(super) struct SetVisitor<'a> { + set_info: &'static SetInfo, + registry: &'a TypeRegistry, +} + +impl<'a> SetVisitor<'a> { + pub fn new(set_info: &'static SetInfo, registry: &'a TypeRegistry) -> Self { + Self { set_info, registry } + } +} + +impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { + type Value = DynamicSet; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected set value") + } + + fn visit_seq(self, mut set: V) -> Result + where + V: SeqAccess<'de>, + { + let mut dynamic_set = DynamicSet::default(); + let value_registration = try_get_registration(self.set_info.value_ty(), self.registry)?; + while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new_internal( + value_registration, + self.registry, + ))? { + dynamic_set.insert_boxed(value); + } + + Ok(dynamic_set) + } +} diff --git a/crates/bevy_reflect/src/serde/de/struct_utils.rs b/crates/bevy_reflect/src/serde/de/struct_utils.rs new file mode 100644 index 0000000000..836d36c0bd --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/struct_utils.rs @@ -0,0 +1,172 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::serde::de::helpers::{ExpectedValues, Ident}; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::{SerializationData, TypedReflectDeserializer}; +use crate::{ + DynamicStruct, NamedField, StructInfo, StructVariantInfo, TypeRegistration, TypeRegistry, +}; +use core::slice::Iter; +use serde::de::{Error, MapAccess, SeqAccess}; + +/// A helper trait for accessing type information from struct-like types. +pub(super) trait StructLikeInfo { + fn field(&self, name: &str) -> Result<&NamedField, E>; + fn field_at(&self, index: usize) -> Result<&NamedField, E>; + fn field_len(&self) -> usize; + fn iter_fields(&self) -> Iter<'_, NamedField>; +} + +impl StructLikeInfo for StructInfo { + fn field(&self, name: &str) -> Result<&NamedField, E> { + Self::field(self, name).ok_or_else(|| { + make_custom_error(format_args!( + "no field named `{}` on struct `{}`", + name, + self.type_path(), + )) + }) + } + + fn field_at(&self, index: usize) -> Result<&NamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + make_custom_error(format_args!( + "no field at index `{}` on struct `{}`", + index, + self.type_path(), + )) + }) + } + + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn iter_fields(&self) -> Iter<'_, NamedField> { + self.iter() + } +} + +impl StructLikeInfo for StructVariantInfo { + fn field(&self, name: &str) -> Result<&NamedField, E> { + Self::field(self, name).ok_or_else(|| { + make_custom_error(format_args!( + "no field named `{}` on variant `{}`", + name, + self.name(), + )) + }) + } + + fn field_at(&self, index: usize) -> Result<&NamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + make_custom_error(format_args!( + "no field at index `{}` on variant `{}`", + index, + self.name(), + )) + }) + } + + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn iter_fields(&self) -> Iter<'_, NamedField> { + self.iter() + } +} + +/// Deserializes a [struct-like] type from a mapping of fields, returning a [`DynamicStruct`]. +/// +/// [struct-like]: StructLikeInfo +pub(super) fn visit_struct<'de, T, V>( + map: &mut V, + info: &'static T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: StructLikeInfo, + V: MapAccess<'de>, +{ + let mut dynamic_struct = DynamicStruct::default(); + while let Some(Ident(key)) = map.next_key::()? { + let field = info.field::(&key).map_err(|_| { + let fields = info.iter_fields().map(NamedField::name); + make_custom_error(format_args!( + "unknown field `{}`, expected one of {:?}", + key, + ExpectedValues::from_iter(fields) + )) + })?; + let registration = try_get_registration(*field.ty(), registry)?; + let value = map.next_value_seed(TypedReflectDeserializer::new_internal( + registration, + registry, + ))?; + dynamic_struct.insert_boxed(&key, value); + } + + if let Some(serialization_data) = registration.data::() { + for (skipped_index, skipped_field) in serialization_data.iter_skipped() { + let Ok(field) = info.field_at::(*skipped_index) else { + continue; + }; + dynamic_struct.insert_boxed( + field.name(), + skipped_field.generate_default().into_partial_reflect(), + ); + } + } + + Ok(dynamic_struct) +} + +/// Deserializes a [struct-like] type from a sequence of fields, returning a [`DynamicStruct`]. +/// +/// [struct-like]: StructLikeInfo +pub(super) fn visit_struct_seq<'de, T, V>( + seq: &mut V, + info: &T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: StructLikeInfo, + V: SeqAccess<'de>, +{ + let mut dynamic_struct = DynamicStruct::default(); + + let len = info.field_len(); + + if len == 0 { + // Handle unit structs + return Ok(dynamic_struct); + } + + let serialization_data = registration.data::(); + + for index in 0..len { + let name = info.field_at::(index)?.name(); + + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or_default() + { + if let Some(value) = serialization_data.unwrap().generate_default(index) { + dynamic_struct.insert_boxed(name, value.into_partial_reflect()); + } + continue; + } + + let value = seq + .next_element_seed(TypedReflectDeserializer::new_internal( + try_get_registration(*info.field_at(index)?.ty(), registry)?, + registry, + ))? + .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; + dynamic_struct.insert_boxed(name, value); + } + + Ok(dynamic_struct) +} diff --git a/crates/bevy_reflect/src/serde/de/structs.rs b/crates/bevy_reflect/src/serde/de/structs.rs new file mode 100644 index 0000000000..85aa6f15b3 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/structs.rs @@ -0,0 +1,50 @@ +use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; +use crate::{DynamicStruct, StructInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{MapAccess, SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Struct`] values. +/// +/// [`Struct`]: crate::Struct +pub(super) struct StructVisitor<'a> { + struct_info: &'static StructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> StructVisitor<'a> { + pub fn new( + struct_info: &'static StructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + struct_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { + type Value = DynamicStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected struct value") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + visit_struct(&mut map, self.struct_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/de/tuple_structs.rs b/crates/bevy_reflect/src/serde/de/tuple_structs.rs new file mode 100644 index 0000000000..8bbe44a324 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuple_structs.rs @@ -0,0 +1,49 @@ +use crate::serde::de::tuple_utils::visit_tuple; +use crate::{DynamicTupleStruct, TupleStructInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`TupleStruct`] values. +/// +/// [`TupleStruct`]: crate::TupleStruct +pub(super) struct TupleStructVisitor<'a> { + tuple_struct_info: &'static TupleStructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TupleStructVisitor<'a> { + pub fn new( + tuple_struct_info: &'static TupleStructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + tuple_struct_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { + type Value = DynamicTupleStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple struct value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple( + &mut seq, + self.tuple_struct_info, + self.registration, + self.registry, + ) + .map(DynamicTupleStruct::from) + } +} diff --git a/crates/bevy_reflect/src/serde/de/tuple_utils.rs b/crates/bevy_reflect/src/serde/de/tuple_utils.rs new file mode 100644 index 0000000000..7dd8ad4481 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuple_utils.rs @@ -0,0 +1,103 @@ +use crate::serde::de::error_utils::make_custom_error; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::{SerializationData, TypedReflectDeserializer}; +use crate::{ + DynamicTuple, TupleInfo, TupleStructInfo, TupleVariantInfo, TypeRegistration, TypeRegistry, + UnnamedField, +}; +use serde::de::{Error, SeqAccess}; + +pub(super) trait TupleLikeInfo { + fn field_at(&self, index: usize) -> Result<&UnnamedField, E>; + fn field_len(&self) -> usize; +} + +impl TupleLikeInfo for TupleInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + make_custom_error(format_args!( + "no field at index `{}` on tuple `{}`", + index, + self.type_path(), + )) + }) + } +} + +impl TupleLikeInfo for TupleStructInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + make_custom_error(format_args!( + "no field at index `{}` on tuple struct `{}`", + index, + self.type_path(), + )) + }) + } +} + +impl TupleLikeInfo for TupleVariantInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + make_custom_error(format_args!( + "no field at index `{}` on tuple variant `{}`", + index, + self.name(), + )) + }) + } +} + +/// Deserializes a [tuple-like] type from a sequence of elements, returning a [`DynamicTuple`]. +/// +/// [tuple-like]: TupleLikeInfo +pub(super) fn visit_tuple<'de, T, V>( + seq: &mut V, + info: &T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: TupleLikeInfo, + V: SeqAccess<'de>, +{ + let mut tuple = DynamicTuple::default(); + + let len = info.field_len(); + + if len == 0 { + // Handle empty tuple/tuple struct + return Ok(tuple); + } + + let serialization_data = registration.data::(); + + for index in 0..len { + if let Some(value) = serialization_data.and_then(|data| data.generate_default(index)) { + tuple.insert_boxed(value.into_partial_reflect()); + continue; + } + + let value = seq + .next_element_seed(TypedReflectDeserializer::new_internal( + try_get_registration(*info.field_at(index)?.ty(), registry)?, + registry, + ))? + .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; + tuple.insert_boxed(value); + } + + Ok(tuple) +} diff --git a/crates/bevy_reflect/src/serde/de/tuples.rs b/crates/bevy_reflect/src/serde/de/tuples.rs new file mode 100644 index 0000000000..de18cbda61 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuples.rs @@ -0,0 +1,43 @@ +use crate::serde::de::tuple_utils::visit_tuple; +use crate::{DynamicTuple, TupleInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Tuple`] values. +/// +/// [`Tuple`]: crate::Tuple +pub(super) struct TupleVisitor<'a> { + tuple_info: &'static TupleInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TupleVisitor<'a> { + pub fn new( + tuple_info: &'static TupleInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + tuple_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> { + type Value = DynamicTuple; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index a962c36bd0..bc3137d0b2 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -142,7 +142,7 @@ mod tests { #[test] #[should_panic( - expected = "cannot serialize dynamic value without represented type: bevy_reflect::DynamicStruct" + expected = "cannot serialize dynamic value without represented type: `bevy_reflect::DynamicStruct`" )] fn should_not_serialize_unproxied_dynamic() { let registry = TypeRegistry::default(); diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs deleted file mode 100644 index 8615171238..0000000000 --- a/crates/bevy_reflect/src/serde/ser.rs +++ /dev/null @@ -1,1012 +0,0 @@ -use crate::{ - Array, Enum, List, Map, PartialReflect, ReflectRef, ReflectSerialize, Set, Struct, Tuple, - TupleStruct, TypeInfo, TypeRegistry, VariantInfo, VariantType, -}; -use serde::ser::{ - Error, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, - SerializeTupleVariant, -}; -use serde::{ - ser::{SerializeMap, SerializeSeq}, - Serialize, -}; - -use super::SerializationData; - -pub enum Serializable<'a> { - Owned(Box), - Borrowed(&'a dyn erased_serde::Serialize), -} - -impl<'a> Serializable<'a> { - #[allow(clippy::should_implement_trait)] - pub fn borrow(&self) -> &dyn erased_serde::Serialize { - match self { - Serializable::Borrowed(serialize) => serialize, - Serializable::Owned(serialize) => serialize, - } - } -} - -fn get_serializable<'a, E: Error>( - reflect_value: &'a dyn PartialReflect, - type_registry: &TypeRegistry, -) -> Result, E> { - let Some(reflect_value) = reflect_value.try_as_reflect() else { - return Err(Error::custom(format_args!( - "Type '{}' does not implement `Reflect`", - reflect_value.reflect_type_path() - ))); - }; - let info = reflect_value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "Type '{}' does not represent any type", - reflect_value.reflect_type_path(), - )) - })?; - - let registration = type_registry.get(info.type_id()).ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` is not registered in the type registry", - info.type_path(), - )) - })?; - - let reflect_serialize = registration.data::().ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", - info.type_path(), - )) - })?; - - Ok(reflect_serialize.get_serializable(reflect_value)) -} - -/// A general purpose serializer for reflected types. -/// -/// This is the serializer counterpart to [`ReflectDeserializer`]. -/// -/// See [`TypedReflectSerializer`] for a serializer that serializes a known type. -/// -/// # Output -/// -/// This serializer will output a map with a single entry, -/// where the key is the _full_ [type path] of the reflected type -/// and the value is the serialized data. -/// -/// # Example -/// -/// ``` -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{TypeRegistry, serde::ReflectSerializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = MyStruct { value: 123 }; -/// -/// let reflect_serializer = ReflectSerializer::new(&input, ®istry); -/// let output = ron::to_string(&reflect_serializer).unwrap(); -/// -/// assert_eq!(output, r#"{"my_crate::MyStruct":(value:123)}"#); -/// ``` -/// -/// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer -/// [type path]: crate::TypePath::type_path -pub struct ReflectSerializer<'a> { - pub value: &'a dyn PartialReflect, - pub registry: &'a TypeRegistry, -} - -impl<'a> ReflectSerializer<'a> { - pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - ReflectSerializer { value, registry } - } -} - -impl<'a> Serialize for ReflectSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(1))?; - state.serialize_entry( - self.value - .get_represented_type_info() - .ok_or_else(|| { - if self.value.is_dynamic() { - Error::custom(format_args!( - "cannot serialize dynamic value without represented type: {}", - self.value.reflect_type_path() - )) - } else { - Error::custom(format_args!( - "cannot get type info for {}", - self.value.reflect_type_path() - )) - } - })? - .type_path(), - &TypedReflectSerializer::new(self.value, self.registry), - )?; - state.end() - } -} - -/// A serializer for reflected types whose type will be known during deserialization. -/// -/// This is the serializer counterpart to [`TypedReflectDeserializer`]. -/// -/// See [`ReflectSerializer`] for a serializer that serializes an unknown type. -/// -/// # Output -/// -/// Since the type is expected to be known during deserialization, -/// this serializer will not output any additional type information, -/// such as the [type path]. -/// -/// Instead, it will output just the serialized data. -/// -/// # Example -/// -/// ``` -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{TypeRegistry, serde::TypedReflectSerializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = MyStruct { value: 123 }; -/// -/// let reflect_serializer = TypedReflectSerializer::new(&input, ®istry); -/// let output = ron::to_string(&reflect_serializer).unwrap(); -/// -/// assert_eq!(output, r#"(value:123)"#); -/// ``` -/// -/// [`TypedReflectDeserializer`]: crate::serde::TypedReflectDeserializer -/// [type path]: crate::TypePath::type_path -pub struct TypedReflectSerializer<'a> { - pub value: &'a dyn PartialReflect, - pub registry: &'a TypeRegistry, -} - -impl<'a> TypedReflectSerializer<'a> { - pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - TypedReflectSerializer { value, registry } - } -} - -impl<'a> Serialize for TypedReflectSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Handle both Value case and types that have a custom `Serialize` - let serializable = get_serializable::(self.value, self.registry); - if let Ok(serializable) = serializable { - return serializable.borrow().serialize(serializer); - } - - match self.value.reflect_ref() { - ReflectRef::Struct(value) => StructSerializer { - struct_value: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::TupleStruct(value) => TupleStructSerializer { - tuple_struct: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Tuple(value) => TupleSerializer { - tuple: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::List(value) => ListSerializer { - list: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Array(value) => ArraySerializer { - array: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Map(value) => MapSerializer { - map: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Set(value) => SetSerializer { - set: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Enum(value) => EnumSerializer { - enum_value: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Value(_) => Err(serializable.err().unwrap()), - } - } -} - -pub struct ReflectValueSerializer<'a> { - pub registry: &'a TypeRegistry, - pub value: &'a dyn PartialReflect, -} - -impl<'a> Serialize for ReflectValueSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - get_serializable::(self.value, self.registry)? - .borrow() - .serialize(serializer) - } -} - -pub struct StructSerializer<'a> { - pub struct_value: &'a dyn Struct, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for StructSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self - .struct_value - .get_represented_type_info() - .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.struct_value.reflect_type_path() - )) - })?; - - let struct_info = match type_info { - TypeInfo::Struct(struct_info) => struct_info, - info => { - return Err(Error::custom(format_args!( - "expected struct type but received {info:?}" - ))); - } - }; - - let serialization_data = self - .registry - .get(type_info.type_id()) - .and_then(|registration| registration.data::()); - let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); - let mut state = serializer.serialize_struct( - struct_info.type_path_table().ident().unwrap(), - self.struct_value.field_len() - ignored_len, - )?; - - for (index, value) in self.struct_value.iter_fields().enumerate() { - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or(false) - { - continue; - } - let key = struct_info.field_at(index).unwrap().name(); - state.serialize_field(key, &TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct TupleStructSerializer<'a> { - pub tuple_struct: &'a dyn TupleStruct, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for TupleStructSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self - .tuple_struct - .get_represented_type_info() - .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.tuple_struct.reflect_type_path() - )) - })?; - - let tuple_struct_info = match type_info { - TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, - info => { - return Err(Error::custom(format_args!( - "expected tuple struct type but received {info:?}" - ))); - } - }; - - let serialization_data = self - .registry - .get(type_info.type_id()) - .and_then(|registration| registration.data::()); - let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); - let mut state = serializer.serialize_tuple_struct( - tuple_struct_info.type_path_table().ident().unwrap(), - self.tuple_struct.field_len() - ignored_len, - )?; - - for (index, value) in self.tuple_struct.iter_fields().enumerate() { - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or(false) - { - continue; - } - state.serialize_field(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct EnumSerializer<'a> { - pub enum_value: &'a dyn Enum, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for EnumSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.enum_value.reflect_type_path() - )) - })?; - - let enum_info = match type_info { - TypeInfo::Enum(enum_info) => enum_info, - info => { - return Err(Error::custom(format_args!( - "expected enum type but received {info:?}" - ))); - } - }; - - let enum_name = enum_info.type_path_table().ident().unwrap(); - let variant_index = self.enum_value.variant_index() as u32; - let variant_info = enum_info - .variant_at(variant_index as usize) - .ok_or_else(|| { - Error::custom(format_args!( - "variant at index `{variant_index}` does not exist", - )) - })?; - let variant_name = variant_info.name(); - let variant_type = self.enum_value.variant_type(); - let field_len = self.enum_value.field_len(); - - match variant_type { - VariantType::Unit => { - if type_info.type_path_table().module_path() == Some("core::option") - && type_info.type_path_table().ident() == Some("Option") - { - serializer.serialize_none() - } else { - serializer.serialize_unit_variant(enum_name, variant_index, variant_name) - } - } - VariantType::Struct => { - let struct_info = match variant_info { - VariantInfo::Struct(struct_info) => struct_info, - info => { - return Err(Error::custom(format_args!( - "expected struct variant type but received {info:?}", - ))); - } - }; - - let mut state = serializer.serialize_struct_variant( - enum_name, - variant_index, - variant_name, - field_len, - )?; - for (index, field) in self.enum_value.iter_fields().enumerate() { - let field_info = struct_info.field_at(index).unwrap(); - state.serialize_field( - field_info.name(), - &TypedReflectSerializer::new(field.value(), self.registry), - )?; - } - state.end() - } - VariantType::Tuple if field_len == 1 => { - let field = self.enum_value.field_at(0).unwrap(); - - if type_info.type_path_table().module_path() == Some("core::option") - && type_info.type_path_table().ident() == Some("Option") - { - serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry)) - } else { - serializer.serialize_newtype_variant( - enum_name, - variant_index, - variant_name, - &TypedReflectSerializer::new(field, self.registry), - ) - } - } - VariantType::Tuple => { - let mut state = serializer.serialize_tuple_variant( - enum_name, - variant_index, - variant_name, - field_len, - )?; - for field in self.enum_value.iter_fields() { - state.serialize_field(&TypedReflectSerializer::new( - field.value(), - self.registry, - ))?; - } - state.end() - } - } - } -} - -pub struct TupleSerializer<'a> { - pub tuple: &'a dyn Tuple, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for TupleSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_tuple(self.tuple.field_len())?; - - for value in self.tuple.iter_fields() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct MapSerializer<'a> { - pub map: &'a dyn Map, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for MapSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(self.map.len()))?; - for (key, value) in self.map.iter() { - state.serialize_entry( - &TypedReflectSerializer::new(key, self.registry), - &TypedReflectSerializer::new(value, self.registry), - )?; - } - state.end() - } -} - -pub struct SetSerializer<'a> { - pub set: &'a dyn Set, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for SetSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_seq(Some(self.set.len()))?; - for value in self.set.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct ListSerializer<'a> { - pub list: &'a dyn List, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ListSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_seq(Some(self.list.len()))?; - for value in self.list.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct ArraySerializer<'a> { - pub array: &'a dyn Array, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ArraySerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_tuple(self.array.len())?; - for value in self.array.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -#[cfg(test)] -mod tests { - use crate::serde::ReflectSerializer; - use crate::{self as bevy_reflect, PartialReflect, Struct}; - use crate::{Reflect, ReflectSerialize, TypeRegistry}; - use bevy_utils::{HashMap, HashSet}; - use ron::extensions::Extensions; - use ron::ser::PrettyConfig; - use serde::Serialize; - use std::f32::consts::PI; - use std::ops::RangeInclusive; - - #[derive(Reflect, Debug, PartialEq)] - struct MyStruct { - primitive_value: i8, - option_value: Option, - option_value_complex: Option, - tuple_value: (f32, usize), - list_value: Vec, - array_value: [i32; 5], - map_value: HashMap, - set_value: HashSet, - struct_value: SomeStruct, - tuple_struct_value: SomeTupleStruct, - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum, - newtype_enum: SomeEnum, - tuple_enum: SomeEnum, - struct_enum: SomeEnum, - ignored_struct: SomeIgnoredStruct, - ignored_tuple_struct: SomeIgnoredTupleStruct, - ignored_struct_variant: SomeIgnoredEnum, - ignored_tuple_variant: SomeIgnoredEnum, - custom_serialize: CustomSerialize, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeStruct { - foo: i64, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeTupleStruct(String); - - #[derive(Reflect, Debug, PartialEq)] - struct SomeUnitStruct; - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredStruct { - #[reflect(ignore)] - ignored: i32, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); - - #[derive(Reflect, Debug, PartialEq)] - enum SomeEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { foo: String }, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeIgnoredEnum { - Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), - Struct { - #[reflect(ignore)] - foo: String, - }, - } - - #[derive(Reflect, Debug, PartialEq, Serialize)] - struct SomeSerializableStruct { - foo: i64, - } - - /// Implements a custom serialize using `#[reflect(Serialize)]`. - /// - /// For testing purposes, this just uses the generated one from deriving Serialize. - #[derive(Reflect, Debug, PartialEq, Serialize)] - #[reflect(Serialize)] - struct CustomSerialize { - value: usize, - #[serde(rename = "renamed")] - inner_struct: SomeSerializableStruct, - } - - fn get_registry() -> TypeRegistry { - let mut registry = TypeRegistry::default(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register_type_data::(); - registry.register::(); - registry.register::>(); - registry.register_type_data::, ReflectSerialize>(); - registry - } - - fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); - map.insert(64, 32); - - let mut set = HashSet::new(); - set.insert(64); - - MyStruct { - primitive_value: 123, - option_value: Some(String::from("Hello world!")), - option_value_complex: Some(SomeStruct { foo: 123 }), - tuple_value: (PI, 1337), - list_value: vec![-2, -1, 0, 1, 2], - array_value: [-2, -1, 0, 1, 2], - map_value: map, - set_value: set, - struct_value: SomeStruct { foo: 999999999 }, - tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum::Unit, - newtype_enum: SomeEnum::NewType(123), - tuple_enum: SomeEnum::Tuple(1.23, 3.21), - struct_enum: SomeEnum::Struct { - foo: String::from("Struct variant value"), - }, - ignored_struct: SomeIgnoredStruct { ignored: 123 }, - ignored_tuple_struct: SomeIgnoredTupleStruct(123), - ignored_struct_variant: SomeIgnoredEnum::Struct { - foo: String::from("Struct Variant"), - }, - ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), - custom_serialize: CustomSerialize { - value: 100, - inner_struct: SomeSerializableStruct { foo: 101 }, - }, - } - } - - #[test] - fn should_serialize() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyStruct": ( - primitive_value: 123, - option_value: Some("Hello world!"), - option_value_complex: Some(( - foo: 123, - )), - tuple_value: (3.1415927, 1337), - list_value: [ - -2, - -1, - 0, - 1, - 2, - ], - array_value: (-2, -1, 0, 1, 2), - map_value: { - 64: 32, - }, - set_value: [ - 64, - ], - struct_value: ( - foo: 999999999, - ), - tuple_struct_value: ("Tuple Struct"), - unit_struct: (), - unit_enum: Unit, - newtype_enum: NewType(123), - tuple_enum: Tuple(1.23, 3.21), - struct_enum: Struct( - foo: "Struct variant value", - ), - ignored_struct: (), - ignored_tuple_struct: (), - ignored_struct_variant: Struct(), - ignored_tuple_variant: Tuple(), - custom_serialize: ( - value: 100, - renamed: ( - foo: 101, - ), - ), - ), -}"#; - assert_eq!(expected, output); - } - - #[test] - fn should_serialize_option() { - #[derive(Reflect, Debug, PartialEq)] - struct OptionTest { - none: Option<()>, - simple: Option, - complex: Option, - } - - let value = OptionTest { - none: None, - simple: Some(String::from("Hello world!")), - complex: Some(SomeStruct { foo: 123 }), - }; - - let registry = get_registry(); - let serializer = ReflectSerializer::new(&value, ®istry); - - // === Normal === // - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::OptionTest": ( - none: None, - simple: Some("Hello world!"), - complex: Some(( - foo: 123, - )), - ), -}"#; - - assert_eq!(expected, output); - - // === Implicit Some === // - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .extensions(Extensions::IMPLICIT_SOME) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"#![enable(implicit_some)] -{ - "bevy_reflect::serde::ser::tests::OptionTest": ( - none: None, - simple: "Hello world!", - complex: ( - foo: 123, - ), - ), -}"#; - - assert_eq!(expected, output); - } - - #[test] - fn enum_should_serialize() { - #[derive(Reflect)] - enum MyEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { value: String }, - } - - let mut registry = get_registry(); - registry.register::(); - - let config = PrettyConfig::default().new_line(String::from("\n")); - - // === Unit Variant === // - let value = MyEnum::Unit; - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Unit, -}"#; - assert_eq!(expected, output); - - // === NewType Variant === // - let value = MyEnum::NewType(123); - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": NewType(123), -}"#; - assert_eq!(expected, output); - - // === Tuple Variant === // - let value = MyEnum::Tuple(1.23, 3.21); - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Tuple(1.23, 3.21), -}"#; - assert_eq!(expected, output); - - // === Struct Variant === // - let value = MyEnum::Struct { - value: String::from("I <3 Enums"), - }; - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Struct( - value: "I <3 Enums", - ), -}"#; - assert_eq!(expected, output); - } - - #[test] - fn should_serialize_non_self_describing_binary() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - let bytes = bincode::serialize(&serializer).unwrap(); - - let expected: Vec = vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, - 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, - 101, 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, - 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, - 0, 0, 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, - 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, - 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, - 0, 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, - 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, - 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, - 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, - 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, - ]; - - assert_eq!(expected, bytes); - } - - #[test] - fn should_serialize_self_describing_binary() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - let bytes: Vec = rmp_serde::to_vec(&serializer).unwrap(); - - let expected: Vec = vec![ - 129, 217, 41, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, - 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, - 121, 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, - 111, 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, - 0, 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, - 145, 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, - 105, 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, - 101, 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, - 99, 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, - 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, - 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, - ]; - - assert_eq!(expected, bytes); - } - - #[test] - fn should_serialize_dynamic_option() { - #[derive(Default, Reflect)] - struct OtherStruct { - some: Option, - none: Option, - } - - let value = OtherStruct { - some: Some(SomeStruct { foo: 999999999 }), - none: None, - }; - let dynamic = value.clone_dynamic(); - let reflect = dynamic.as_partial_reflect(); - - let registry = get_registry(); - - let serializer = ReflectSerializer::new(reflect, ®istry); - - let mut buf = Vec::new(); - - let format = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut ser = serde_json::Serializer::with_formatter(&mut buf, format); - - serializer.serialize(&mut ser).unwrap(); - - let output = std::str::from_utf8(&buf).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::OtherStruct": { - "some": { - "foo": 999999999 - }, - "none": null - } -}"#; - - assert_eq!(expected, output); - } - - #[test] - fn should_return_error_if_missing_registration() { - let value = RangeInclusive::::new(0.0, 1.0); - let registry = TypeRegistry::new(); - - let serializer = ReflectSerializer::new(&value, ®istry); - let error = ron::ser::to_string(&serializer).unwrap_err(); - assert_eq!( - error, - ron::Error::Message( - "Type `core::ops::RangeInclusive` is not registered in the type registry" - .to_string() - ) - ); - } - - #[test] - fn should_return_error_if_missing_type_data() { - let value = RangeInclusive::::new(0.0, 1.0); - let mut registry = TypeRegistry::new(); - registry.register::>(); - - let serializer = ReflectSerializer::new(&value, ®istry); - let error = ron::ser::to_string(&serializer).unwrap_err(); - assert_eq!( - error, - ron::Error::Message( - "Type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() - ) - ); - } -} diff --git a/crates/bevy_reflect/src/serde/ser/arrays.rs b/crates/bevy_reflect/src/serde/ser/arrays.rs new file mode 100644 index 0000000000..b318047ee2 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/arrays.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Array, TypeRegistry}; +use serde::ser::SerializeTuple; +use serde::Serialize; + +/// A serializer for [`Array`] values. +pub(super) struct ArraySerializer<'a> { + array: &'a dyn Array, + registry: &'a TypeRegistry, +} + +impl<'a> ArraySerializer<'a> { + pub fn new(array: &'a dyn Array, registry: &'a TypeRegistry) -> Self { + Self { array, registry } + } +} + +impl<'a> Serialize for ArraySerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_tuple(self.array.len())?; + for value in self.array.iter() { + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/enums.rs b/crates/bevy_reflect/src/serde/ser/enums.rs new file mode 100644 index 0000000000..6951617cb6 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/enums.rs @@ -0,0 +1,125 @@ +use crate::serde::ser::error_utils::make_custom_error; +use crate::serde::TypedReflectSerializer; +use crate::{Enum, TypeInfo, TypeRegistry, VariantInfo, VariantType}; +use serde::ser::{SerializeStructVariant, SerializeTupleVariant}; +use serde::Serialize; + +/// A serializer for [`Enum`] values. +pub(super) struct EnumSerializer<'a> { + enum_value: &'a dyn Enum, + registry: &'a TypeRegistry, +} + +impl<'a> EnumSerializer<'a> { + pub fn new(enum_value: &'a dyn Enum, registry: &'a TypeRegistry) -> Self { + Self { + enum_value, + registry, + } + } +} + +impl<'a> Serialize for EnumSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { + make_custom_error(format_args!( + "cannot get type info for `{}`", + self.enum_value.reflect_type_path() + )) + })?; + + let enum_info = match type_info { + TypeInfo::Enum(enum_info) => enum_info, + info => { + return Err(make_custom_error(format_args!( + "expected enum type but received {info:?}" + ))); + } + }; + + let enum_name = enum_info.type_path_table().ident().unwrap(); + let variant_index = self.enum_value.variant_index() as u32; + let variant_info = enum_info + .variant_at(variant_index as usize) + .ok_or_else(|| { + make_custom_error(format_args!( + "variant at index `{variant_index}` does not exist", + )) + })?; + let variant_name = variant_info.name(); + let variant_type = self.enum_value.variant_type(); + let field_len = self.enum_value.field_len(); + + match variant_type { + VariantType::Unit => { + if type_info.type_path_table().module_path() == Some("core::option") + && type_info.type_path_table().ident() == Some("Option") + { + serializer.serialize_none() + } else { + serializer.serialize_unit_variant(enum_name, variant_index, variant_name) + } + } + VariantType::Struct => { + let struct_info = match variant_info { + VariantInfo::Struct(struct_info) => struct_info, + info => { + return Err(make_custom_error(format_args!( + "expected struct variant type but received {info:?}", + ))); + } + }; + + let mut state = serializer.serialize_struct_variant( + enum_name, + variant_index, + variant_name, + field_len, + )?; + for (index, field) in self.enum_value.iter_fields().enumerate() { + let field_info = struct_info.field_at(index).unwrap(); + state.serialize_field( + field_info.name(), + &TypedReflectSerializer::new_internal(field.value(), self.registry), + )?; + } + state.end() + } + VariantType::Tuple if field_len == 1 => { + let field = self.enum_value.field_at(0).unwrap(); + + if type_info.type_path_table().module_path() == Some("core::option") + && type_info.type_path_table().ident() == Some("Option") + { + serializer + .serialize_some(&TypedReflectSerializer::new_internal(field, self.registry)) + } else { + serializer.serialize_newtype_variant( + enum_name, + variant_index, + variant_name, + &TypedReflectSerializer::new_internal(field, self.registry), + ) + } + } + VariantType::Tuple => { + let mut state = serializer.serialize_tuple_variant( + enum_name, + variant_index, + variant_name, + field_len, + )?; + for field in self.enum_value.iter_fields() { + state.serialize_field(&TypedReflectSerializer::new_internal( + field.value(), + self.registry, + ))?; + } + state.end() + } + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/error_utils.rs b/crates/bevy_reflect/src/serde/ser/error_utils.rs new file mode 100644 index 0000000000..58a029f986 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/error_utils.rs @@ -0,0 +1,26 @@ +use core::fmt::Display; +use serde::ser::Error; + +#[cfg(feature = "debug_stack")] +thread_local! { + /// The thread-local [`TypeInfoStack`] used for debugging. + /// + /// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack + pub(super) static TYPE_INFO_STACK: std::cell::RefCell = const { std::cell::RefCell::new( + crate::type_info_stack::TypeInfoStack::new() + ) }; +} + +/// A helper function for generating a custom serialization error message. +/// +/// This function should be preferred over [`Error::custom`] as it will include +/// other useful information, such as the [type info stack]. +/// +/// [type info stack]: crate::type_info_stack::TypeInfoStack +pub(super) fn make_custom_error(msg: impl Display) -> E { + #[cfg(feature = "debug_stack")] + return TYPE_INFO_STACK + .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + #[cfg(not(feature = "debug_stack"))] + return E::custom(msg); +} diff --git a/crates/bevy_reflect/src/serde/ser/lists.rs b/crates/bevy_reflect/src/serde/ser/lists.rs new file mode 100644 index 0000000000..ad3bb1a040 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/lists.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{List, TypeRegistry}; +use serde::ser::SerializeSeq; +use serde::Serialize; + +/// A serializer for [`List`] values. +pub(super) struct ListSerializer<'a> { + list: &'a dyn List, + registry: &'a TypeRegistry, +} + +impl<'a> ListSerializer<'a> { + pub fn new(list: &'a dyn List, registry: &'a TypeRegistry) -> Self { + Self { list, registry } + } +} + +impl<'a> Serialize for ListSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.list.len()))?; + for value in self.list.iter() { + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/maps.rs b/crates/bevy_reflect/src/serde/ser/maps.rs new file mode 100644 index 0000000000..d5493cd35e --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/maps.rs @@ -0,0 +1,32 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Map, TypeRegistry}; +use serde::ser::SerializeMap; +use serde::Serialize; + +/// A serializer for [`Map`] values. +pub(super) struct MapSerializer<'a> { + map: &'a dyn Map, + registry: &'a TypeRegistry, +} + +impl<'a> MapSerializer<'a> { + pub fn new(map: &'a dyn Map, registry: &'a TypeRegistry) -> Self { + Self { map, registry } + } +} + +impl<'a> Serialize for MapSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(self.map.len()))?; + for (key, value) in self.map.iter() { + state.serialize_entry( + &TypedReflectSerializer::new_internal(key, self.registry), + &TypedReflectSerializer::new_internal(value, self.registry), + )?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs new file mode 100644 index 0000000000..ee2a283e82 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -0,0 +1,518 @@ +pub use serializable::*; +pub use serializer::*; + +mod arrays; +mod enums; +mod error_utils; +mod lists; +mod maps; +mod serializable; +mod serializer; +mod sets; +mod structs; +mod tuple_structs; +mod tuples; + +#[cfg(test)] +mod tests { + use crate::serde::ReflectSerializer; + use crate::{self as bevy_reflect, PartialReflect, Struct}; + use crate::{Reflect, ReflectSerialize, TypeRegistry}; + use bevy_utils::{HashMap, HashSet}; + use ron::extensions::Extensions; + use ron::ser::PrettyConfig; + use serde::Serialize; + use std::f32::consts::PI; + use std::ops::RangeInclusive; + + #[derive(Reflect, Debug, PartialEq)] + struct MyStruct { + primitive_value: i8, + option_value: Option, + option_value_complex: Option, + tuple_value: (f32, usize), + list_value: Vec, + array_value: [i32; 5], + map_value: HashMap, + set_value: HashSet, + struct_value: SomeStruct, + tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum, + newtype_enum: SomeEnum, + tuple_enum: SomeEnum, + struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, + custom_serialize: CustomSerialize, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeStruct { + foo: i64, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeTupleStruct(String); + + #[derive(Reflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + + #[derive(Reflect, Debug, PartialEq)] + enum SomeEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { foo: String }, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + + #[derive(Reflect, Debug, PartialEq, Serialize)] + struct SomeSerializableStruct { + foo: i64, + } + + /// Implements a custom serialize using `#[reflect(Serialize)]`. + /// + /// For testing purposes, this just uses the generated one from deriving Serialize. + #[derive(Reflect, Debug, PartialEq, Serialize)] + #[reflect(Serialize)] + struct CustomSerialize { + value: usize, + #[serde(rename = "renamed")] + inner_struct: SomeSerializableStruct, + } + + fn get_registry() -> TypeRegistry { + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register_type_data::(); + registry.register::(); + registry.register::>(); + registry.register_type_data::, ReflectSerialize>(); + registry + } + + fn get_my_struct() -> MyStruct { + let mut map = HashMap::new(); + map.insert(64, 32); + + let mut set = HashSet::new(); + set.insert(64); + + MyStruct { + primitive_value: 123, + option_value: Some(String::from("Hello world!")), + option_value_complex: Some(SomeStruct { foo: 123 }), + tuple_value: (PI, 1337), + list_value: vec![-2, -1, 0, 1, 2], + array_value: [-2, -1, 0, 1, 2], + map_value: map, + set_value: set, + struct_value: SomeStruct { foo: 999999999 }, + tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum::Unit, + newtype_enum: SomeEnum::NewType(123), + tuple_enum: SomeEnum::Tuple(1.23, 3.21), + struct_enum: SomeEnum::Struct { + foo: String::from("Struct variant value"), + }, + ignored_struct: SomeIgnoredStruct { ignored: 123 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(123), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::from("Struct Variant"), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), + custom_serialize: CustomSerialize { + value: 100, + inner_struct: SomeSerializableStruct { foo: 101 }, + }, + } + } + + #[test] + fn should_serialize() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyStruct": ( + primitive_value: 123, + option_value: Some("Hello world!"), + option_value_complex: Some(( + foo: 123, + )), + tuple_value: (3.1415927, 1337), + list_value: [ + -2, + -1, + 0, + 1, + 2, + ], + array_value: (-2, -1, 0, 1, 2), + map_value: { + 64: 32, + }, + set_value: [ + 64, + ], + struct_value: ( + foo: 999999999, + ), + tuple_struct_value: ("Tuple Struct"), + unit_struct: (), + unit_enum: Unit, + newtype_enum: NewType(123), + tuple_enum: Tuple(1.23, 3.21), + struct_enum: Struct( + foo: "Struct variant value", + ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), + custom_serialize: ( + value: 100, + renamed: ( + foo: 101, + ), + ), + ), +}"#; + assert_eq!(expected, output); + } + + #[test] + fn should_serialize_option() { + #[derive(Reflect, Debug, PartialEq)] + struct OptionTest { + none: Option<()>, + simple: Option, + complex: Option, + } + + let value = OptionTest { + none: None, + simple: Some(String::from("Hello world!")), + complex: Some(SomeStruct { foo: 123 }), + }; + + let registry = get_registry(); + let serializer = ReflectSerializer::new(&value, ®istry); + + // === Normal === // + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::OptionTest": ( + none: None, + simple: Some("Hello world!"), + complex: Some(( + foo: 123, + )), + ), +}"#; + + assert_eq!(expected, output); + + // === Implicit Some === // + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .extensions(Extensions::IMPLICIT_SOME) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"#![enable(implicit_some)] +{ + "bevy_reflect::serde::ser::tests::OptionTest": ( + none: None, + simple: "Hello world!", + complex: ( + foo: 123, + ), + ), +}"#; + + assert_eq!(expected, output); + } + + #[test] + fn enum_should_serialize() { + #[derive(Reflect)] + enum MyEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { value: String }, + } + + let mut registry = get_registry(); + registry.register::(); + + let config = PrettyConfig::default().new_line(String::from("\n")); + + // === Unit Variant === // + let value = MyEnum::Unit; + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Unit, +}"#; + assert_eq!(expected, output); + + // === NewType Variant === // + let value = MyEnum::NewType(123); + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": NewType(123), +}"#; + assert_eq!(expected, output); + + // === Tuple Variant === // + let value = MyEnum::Tuple(1.23, 3.21); + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Tuple(1.23, 3.21), +}"#; + assert_eq!(expected, output); + + // === Struct Variant === // + let value = MyEnum::Struct { + value: String::from("I <3 Enums"), + }; + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Struct( + value: "I <3 Enums", + ), +}"#; + assert_eq!(expected, output); + } + + #[test] + fn should_serialize_non_self_describing_binary() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + let bytes = bincode::serialize(&serializer).unwrap(); + + let expected: Vec = vec![ + 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, + 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, + 101, 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, + 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, + 0, 0, 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, + 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, + 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, + 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, + 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, + 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, + 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + ]; + + assert_eq!(expected, bytes); + } + + #[test] + fn should_serialize_self_describing_binary() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + let bytes: Vec = rmp_serde::to_vec(&serializer).unwrap(); + + let expected: Vec = vec![ + 129, 217, 41, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, + 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, + 121, 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, + 111, 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, + 0, 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, + 145, 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, + 105, 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, + 101, 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, + 99, 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, + 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, + 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, + ]; + + assert_eq!(expected, bytes); + } + + #[test] + fn should_serialize_dynamic_option() { + #[derive(Default, Reflect)] + struct OtherStruct { + some: Option, + none: Option, + } + + let value = OtherStruct { + some: Some(SomeStruct { foo: 999999999 }), + none: None, + }; + let dynamic = value.clone_dynamic(); + let reflect = dynamic.as_partial_reflect(); + + let registry = get_registry(); + + let serializer = ReflectSerializer::new(reflect, ®istry); + + let mut buf = Vec::new(); + + let format = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, format); + + serializer.serialize(&mut ser).unwrap(); + + let output = std::str::from_utf8(&buf).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::OtherStruct": { + "some": { + "foo": 999999999 + }, + "none": null + } +}"#; + + assert_eq!(expected, output); + } + + #[test] + fn should_return_error_if_missing_registration() { + let value = RangeInclusive::::new(0.0, 1.0); + let registry = TypeRegistry::new(); + + let serializer = ReflectSerializer::new(&value, ®istry); + let error = ron::ser::to_string(&serializer).unwrap_err(); + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` is not registered in the type registry (stack: `core::ops::RangeInclusive`)" + .to_string(), + ) + ); + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` is not registered in the type registry" + .to_string(), + ) + ); + } + + #[test] + fn should_return_error_if_missing_type_data() { + let value = RangeInclusive::::new(0.0, 1.0); + let mut registry = TypeRegistry::new(); + registry.register::>(); + + let serializer = ReflectSerializer::new(&value, ®istry); + let error = ron::ser::to_string(&serializer).unwrap_err(); + #[cfg(feature = "debug_stack")] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive`)".to_string() + ) + ); + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() + ) + ); + } + + #[cfg(feature = "debug_stack")] + mod debug_stack { + use super::*; + + #[test] + fn should_report_context_in_errors() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect)] + struct Bar { + some_other_field: Option, + baz: Baz, + } + + #[derive(Reflect)] + struct Baz { + value: Vec>, + } + + let value = Foo { + bar: Bar { + some_other_field: Some(123), + baz: Baz { + value: vec![0.0..=1.0], + }, + }, + }; + + let registry = TypeRegistry::new(); + let serializer = ReflectSerializer::new(&value, ®istry); + + let error = ron::ser::to_string(&serializer).unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` is not registered in the type registry (stack: `bevy_reflect::serde::ser::tests::debug_stack::Foo` -> `bevy_reflect::serde::ser::tests::debug_stack::Bar` -> `bevy_reflect::serde::ser::tests::debug_stack::Baz` -> `alloc::vec::Vec>` -> `core::ops::RangeInclusive`)".to_string() + ) + ); + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/serializable.rs b/crates/bevy_reflect/src/serde/ser/serializable.rs new file mode 100644 index 0000000000..9cf96ee77a --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/serializable.rs @@ -0,0 +1,62 @@ +use crate::serde::ser::error_utils::make_custom_error; +use crate::{PartialReflect, ReflectSerialize, TypeRegistry}; +use serde::ser::Error; +use std::ops::Deref; + +/// A type-erased serializable value. +pub enum Serializable<'a> { + Owned(Box), + Borrowed(&'a dyn erased_serde::Serialize), +} + +impl<'a> Serializable<'a> { + /// Attempts to create a [`Serializable`] from a [`PartialReflect`] value. + /// + /// Returns an error if any of the following conditions are met: + /// - The underlying type of `value` does not implement [`Reflect`]. + /// - The underlying type of `value` does not represent any type (via [`PartialReflect::get_represented_type_info`]). + /// - The represented type of `value` is not registered in the `type_registry`. + /// - The represented type of `value` did not register the [`ReflectSerialize`] type data. + /// + /// [`Reflect`]: crate::Reflect + pub fn try_from_reflect_value( + value: &'a dyn PartialReflect, + type_registry: &TypeRegistry, + ) -> Result, E> { + let value = value.try_as_reflect().ok_or_else(|| { + make_custom_error(format_args!( + "type `{}` does not implement `Reflect`", + value.reflect_type_path() + )) + })?; + + let info = value.reflect_type_info(); + + let registration = type_registry.get(info.type_id()).ok_or_else(|| { + make_custom_error(format_args!( + "type `{}` is not registered in the type registry", + info.type_path(), + )) + })?; + + let reflect_serialize = registration.data::().ok_or_else(|| { + make_custom_error(format_args!( + "type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + info.type_path(), + )) + })?; + + Ok(reflect_serialize.get_serializable(value)) + } +} + +impl<'a> Deref for Serializable<'a> { + type Target = dyn erased_serde::Serialize + 'a; + + fn deref(&self) -> &Self::Target { + match self { + Serializable::Borrowed(serialize) => serialize, + Serializable::Owned(serialize) => serialize, + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/serializer.rs b/crates/bevy_reflect/src/serde/ser/serializer.rs new file mode 100644 index 0000000000..cdc00452aa --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/serializer.rs @@ -0,0 +1,210 @@ +use crate::serde::ser::arrays::ArraySerializer; +use crate::serde::ser::enums::EnumSerializer; +use crate::serde::ser::error_utils::make_custom_error; +#[cfg(feature = "debug_stack")] +use crate::serde::ser::error_utils::TYPE_INFO_STACK; +use crate::serde::ser::lists::ListSerializer; +use crate::serde::ser::maps::MapSerializer; +use crate::serde::ser::sets::SetSerializer; +use crate::serde::ser::structs::StructSerializer; +use crate::serde::ser::tuple_structs::TupleStructSerializer; +use crate::serde::ser::tuples::TupleSerializer; +use crate::serde::Serializable; +use crate::{PartialReflect, ReflectRef, TypeRegistry}; +use serde::ser::SerializeMap; +use serde::Serialize; + +/// A general purpose serializer for reflected types. +/// +/// This is the serializer counterpart to [`ReflectDeserializer`]. +/// +/// See [`TypedReflectSerializer`] for a serializer that serializes a known type. +/// +/// # Output +/// +/// This serializer will output a map with a single entry, +/// where the key is the _full_ [type path] of the reflected type +/// and the value is the serialized data. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{TypeRegistry, serde::ReflectSerializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = MyStruct { value: 123 }; +/// +/// let reflect_serializer = ReflectSerializer::new(&input, ®istry); +/// let output = ron::to_string(&reflect_serializer).unwrap(); +/// +/// assert_eq!(output, r#"{"my_crate::MyStruct":(value:123)}"#); +/// ``` +/// +/// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer +/// [type path]: crate::TypePath::type_path +pub struct ReflectSerializer<'a> { + value: &'a dyn PartialReflect, + registry: &'a TypeRegistry, +} + +impl<'a> ReflectSerializer<'a> { + pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + Self { value, registry } + } +} + +impl<'a> Serialize for ReflectSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(1))?; + state.serialize_entry( + self.value + .get_represented_type_info() + .ok_or_else(|| { + if self.value.is_dynamic() { + make_custom_error(format_args!( + "cannot serialize dynamic value without represented type: `{}`", + self.value.reflect_type_path() + )) + } else { + make_custom_error(format_args!( + "cannot get type info for `{}`", + self.value.reflect_type_path() + )) + } + })? + .type_path(), + &TypedReflectSerializer::new(self.value, self.registry), + )?; + state.end() + } +} + +/// A serializer for reflected types whose type will be known during deserialization. +/// +/// This is the serializer counterpart to [`TypedReflectDeserializer`]. +/// +/// See [`ReflectSerializer`] for a serializer that serializes an unknown type. +/// +/// # Output +/// +/// Since the type is expected to be known during deserialization, +/// this serializer will not output any additional type information, +/// such as the [type path]. +/// +/// Instead, it will output just the serialized data. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{TypeRegistry, serde::TypedReflectSerializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = MyStruct { value: 123 }; +/// +/// let reflect_serializer = TypedReflectSerializer::new(&input, ®istry); +/// let output = ron::to_string(&reflect_serializer).unwrap(); +/// +/// assert_eq!(output, r#"(value:123)"#); +/// ``` +/// +/// [`TypedReflectDeserializer`]: crate::serde::TypedReflectDeserializer +/// [type path]: crate::TypePath::type_path +pub struct TypedReflectSerializer<'a> { + value: &'a dyn PartialReflect, + registry: &'a TypeRegistry, +} + +impl<'a> TypedReflectSerializer<'a> { + pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new()); + + Self { value, registry } + } + + /// An internal constructor for creating a serializer without resetting the type info stack. + pub(super) fn new_internal(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + Self { value, registry } + } +} + +impl<'a> Serialize for TypedReflectSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[cfg(feature = "debug_stack")] + { + let info = self.value.get_represented_type_info().ok_or_else(|| { + make_custom_error(format_args!( + "type `{}` does not represent any type", + self.value.reflect_type_path(), + )) + })?; + + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + } + + // Handle both Value case and types that have a custom `Serialize` + let serializable = + Serializable::try_from_reflect_value::(self.value, self.registry); + if let Ok(serializable) = serializable { + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + + return serializable.serialize(serializer); + } + + let output = match self.value.reflect_ref() { + ReflectRef::Struct(value) => { + StructSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::TupleStruct(value) => { + TupleStructSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Tuple(value) => { + TupleSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::List(value) => { + ListSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Array(value) => { + ArraySerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Map(value) => { + MapSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Set(value) => { + SetSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Enum(value) => { + EnumSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Value(_) => Err(serializable.err().unwrap()), + }; + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + + output + } +} diff --git a/crates/bevy_reflect/src/serde/ser/sets.rs b/crates/bevy_reflect/src/serde/ser/sets.rs new file mode 100644 index 0000000000..846f9e4f84 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/sets.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Set, TypeRegistry}; +use serde::ser::SerializeSeq; +use serde::Serialize; + +/// A serializer for [`Set`] values. +pub(super) struct SetSerializer<'a> { + set: &'a dyn Set, + registry: &'a TypeRegistry, +} + +impl<'a> SetSerializer<'a> { + pub fn new(set: &'a dyn Set, registry: &'a TypeRegistry) -> Self { + Self { set, registry } + } +} + +impl<'a> Serialize for SetSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.set.len()))?; + for value in self.set.iter() { + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/structs.rs b/crates/bevy_reflect/src/serde/ser/structs.rs new file mode 100644 index 0000000000..7763e297fe --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/structs.rs @@ -0,0 +1,71 @@ +use crate::serde::ser::error_utils::make_custom_error; +use crate::serde::{SerializationData, TypedReflectSerializer}; +use crate::{Struct, TypeInfo, TypeRegistry}; +use serde::ser::SerializeStruct; +use serde::Serialize; + +/// A serializer for [`Struct`] values. +pub(super) struct StructSerializer<'a> { + struct_value: &'a dyn Struct, + registry: &'a TypeRegistry, +} + +impl<'a> StructSerializer<'a> { + pub fn new(struct_value: &'a dyn Struct, registry: &'a TypeRegistry) -> Self { + Self { + struct_value, + registry, + } + } +} + +impl<'a> Serialize for StructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self + .struct_value + .get_represented_type_info() + .ok_or_else(|| { + make_custom_error(format_args!( + "cannot get type info for `{}`", + self.struct_value.reflect_type_path() + )) + })?; + + let struct_info = match type_info { + TypeInfo::Struct(struct_info) => struct_info, + info => { + return Err(make_custom_error(format_args!( + "expected struct type but received {info:?}" + ))); + } + }; + + let serialization_data = self + .registry + .get(type_info.type_id()) + .and_then(|registration| registration.data::()); + let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); + let mut state = serializer.serialize_struct( + struct_info.type_path_table().ident().unwrap(), + self.struct_value.field_len() - ignored_len, + )?; + + for (index, value) in self.struct_value.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or(false) + { + continue; + } + let key = struct_info.field_at(index).unwrap().name(); + state.serialize_field( + key, + &TypedReflectSerializer::new_internal(value, self.registry), + )?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/tuple_structs.rs b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs new file mode 100644 index 0000000000..625e41e116 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs @@ -0,0 +1,67 @@ +use crate::serde::ser::error_utils::make_custom_error; +use crate::serde::{SerializationData, TypedReflectSerializer}; +use crate::{TupleStruct, TypeInfo, TypeRegistry}; +use serde::ser::SerializeTupleStruct; +use serde::Serialize; + +/// A serializer for [`TupleStruct`] values. +pub(super) struct TupleStructSerializer<'a> { + tuple_struct: &'a dyn TupleStruct, + registry: &'a TypeRegistry, +} + +impl<'a> TupleStructSerializer<'a> { + pub fn new(tuple_struct: &'a dyn TupleStruct, registry: &'a TypeRegistry) -> Self { + Self { + tuple_struct, + registry, + } + } +} + +impl<'a> Serialize for TupleStructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self + .tuple_struct + .get_represented_type_info() + .ok_or_else(|| { + make_custom_error(format_args!( + "cannot get type info for `{}`", + self.tuple_struct.reflect_type_path() + )) + })?; + + let tuple_struct_info = match type_info { + TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, + info => { + return Err(make_custom_error(format_args!( + "expected tuple struct type but received {info:?}" + ))); + } + }; + + let serialization_data = self + .registry + .get(type_info.type_id()) + .and_then(|registration| registration.data::()); + let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); + let mut state = serializer.serialize_tuple_struct( + tuple_struct_info.type_path_table().ident().unwrap(), + self.tuple_struct.field_len() - ignored_len, + )?; + + for (index, value) in self.tuple_struct.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or(false) + { + continue; + } + state.serialize_field(&TypedReflectSerializer::new_internal(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/tuples.rs b/crates/bevy_reflect/src/serde/ser/tuples.rs new file mode 100644 index 0000000000..e106149613 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/tuples.rs @@ -0,0 +1,30 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Tuple, TypeRegistry}; +use serde::ser::SerializeTuple; +use serde::Serialize; + +/// A serializer for [`Tuple`] values. +pub(super) struct TupleSerializer<'a> { + tuple: &'a dyn Tuple, + registry: &'a TypeRegistry, +} + +impl<'a> TupleSerializer<'a> { + pub fn new(tuple: &'a dyn Tuple, registry: &'a TypeRegistry) -> Self { + Self { tuple, registry } + } +} + +impl<'a> Serialize for TupleSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_tuple(self.tuple.field_len())?; + + for value in self.tuple.iter_fields() { + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index d3450239e6..fdd59396da 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -14,6 +14,9 @@ use thiserror::Error; /// This trait is automatically implemented by the [`#[derive(Reflect)]`](derive@crate::Reflect) macro /// and allows type information to be processed without an instance of that type. /// +/// If you need to use this trait as a generic bound along with other reflection traits, +/// for your convenience, consider using [`Reflectable`] instead. +/// /// # Implementing /// /// While it is recommended to leave implementing this trait to the `#[derive(Reflect)]` macro, @@ -81,9 +84,10 @@ use thiserror::Error; /// # } /// ``` /// +/// [`Reflectable`]: crate::Reflectable /// [utility]: crate::utility #[diagnostic::on_unimplemented( - message = "`{Self}` can not provide type information through reflection", + message = "`{Self}` does not implement `Typed` so cannot provide static type information", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait Typed: Reflect + TypePath { @@ -103,6 +107,10 @@ pub trait Typed: Reflect + TypePath { /// This trait has a blanket implementation for all types that implement `Typed` /// and manual implementations for all dynamic types (which simply return `None`). #[doc(hidden)] +#[diagnostic::on_unimplemented( + message = "`{Self}` does not implement `Typed` so cannot provide static type information", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" +)] pub trait MaybeTyped: PartialReflect { /// Returns the compile-time [info] for the underlying type, if it exists. /// @@ -132,6 +140,27 @@ impl MaybeTyped for DynamicArray {} impl MaybeTyped for DynamicTuple {} +/// Dynamic dispatch for [`Typed`]. +/// +/// Since this is a supertrait of [`Reflect`] its methods can be called on a `dyn Reflect`. +/// +/// [`Reflect`]: crate::Reflect +#[diagnostic::on_unimplemented( + message = "`{Self}` can not provide dynamic type information through reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" +)] +pub trait DynamicTyped { + /// See [`Typed::type_info`]. + fn reflect_type_info(&self) -> &'static TypeInfo; +} + +impl DynamicTyped for T { + #[inline] + fn reflect_type_info(&self) -> &'static TypeInfo { + Self::type_info() + } +} + /// A [`TypeInfo`]-specific error. #[derive(Debug, Error)] pub enum TypeInfoError { @@ -147,15 +176,17 @@ pub enum TypeInfoError { /// Compile-time type information for various reflected types. /// -/// Generally, for any given type, this value can be retrieved one of three ways: +/// Generally, for any given type, this value can be retrieved in one of four ways: /// /// 1. [`Typed::type_info`] -/// 2. [`PartialReflect::get_represented_type_info`] -/// 3. [`TypeRegistry::get_type_info`] +/// 2. [`DynamicTyped::reflect_type_info`] +/// 3. [`PartialReflect::get_represented_type_info`] +/// 4. [`TypeRegistry::get_type_info`] /// /// Each return a static reference to [`TypeInfo`], but they all have their own use cases. /// For example, if you know the type at compile time, [`Typed::type_info`] is probably -/// the simplest. If all you have is a `dyn PartialReflect`, you'll probably want [`PartialReflect::get_represented_type_info`]. +/// the simplest. If you have a `dyn Reflect` you can use [`DynamicTyped::reflect_type_info`]. +/// If all you have is a `dyn PartialReflect`, you'll probably want [`PartialReflect::get_represented_type_info`]. /// Lastly, if all you have is a [`TypeId`] or [type path], you will need to go through /// [`TypeRegistry::get_type_info`]. /// diff --git a/crates/bevy_reflect/src/type_info_stack.rs b/crates/bevy_reflect/src/type_info_stack.rs new file mode 100644 index 0000000000..f7f22a54ac --- /dev/null +++ b/crates/bevy_reflect/src/type_info_stack.rs @@ -0,0 +1,49 @@ +use crate::TypeInfo; +use core::fmt::{Debug, Formatter}; +use core::slice::Iter; + +/// Helper struct for managing a stack of [`TypeInfo`] instances. +/// +/// This is useful for tracking the type hierarchy when serializing and deserializing types. +#[derive(Default, Clone)] +pub(crate) struct TypeInfoStack { + stack: Vec<&'static TypeInfo>, +} + +impl TypeInfoStack { + /// Create a new empty [`TypeInfoStack`]. + pub const fn new() -> Self { + Self { stack: Vec::new() } + } + + /// Push a new [`TypeInfo`] onto the stack. + pub fn push(&mut self, type_info: &'static TypeInfo) { + self.stack.push(type_info); + } + + /// Pop the last [`TypeInfo`] off the stack. + pub fn pop(&mut self) { + self.stack.pop(); + } + + /// Get an iterator over the stack in the order they were pushed. + pub fn iter(&self) -> Iter<&'static TypeInfo> { + self.stack.iter() + } +} + +impl Debug for TypeInfoStack { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut iter = self.iter(); + + if let Some(first) = iter.next() { + write!(f, "`{}`", first.type_path())?; + } + + for info in iter { + write!(f, " -> `{}`", info.type_path())?; + } + + Ok(()) + } +} diff --git a/crates/bevy_reflect/src/type_path.rs b/crates/bevy_reflect/src/type_path.rs index dd2e18cc12..bc685eb9f9 100644 --- a/crates/bevy_reflect/src/type_path.rs +++ b/crates/bevy_reflect/src/type_path.rs @@ -80,7 +80,7 @@ use std::fmt; /// [`module_path`]: TypePath::module_path /// [`type_ident`]: TypePath::type_ident #[diagnostic::on_unimplemented( - message = "`{Self}` does not have a type path", + message = "`{Self}` does not implement `TypePath` so cannot provide static type path information", note = "consider annotating `{Self}` with `#[derive(Reflect)]` or `#[derive(TypePath)]`" )] pub trait TypePath: 'static { @@ -134,7 +134,7 @@ pub trait TypePath: 'static { /// /// [`Reflect`]: crate::Reflect #[diagnostic::on_unimplemented( - message = "`{Self}` can not be used as a dynamic type path", + message = "`{Self}` does not implement `TypePath` so cannot provide dynamic type path information", note = "consider annotating `{Self}` with `#[derive(Reflect)]` or `#[derive(TypePath)]`" )] pub trait DynamicTypePath { diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 1263b0bbed..fa74e51950 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -53,11 +53,15 @@ impl Debug for TypeRegistryArc { /// This trait is automatically implemented for items using [`#[derive(Reflect)]`](derive@crate::Reflect). /// The macro also allows [`TypeData`] to be more easily registered. /// +/// If you need to use this trait as a generic bound along with other reflection traits, +/// for your convenience, consider using [`Reflectable`] instead. +/// /// See the [crate-level documentation] for more information on type registration. /// +/// [`Reflectable`]: crate::Reflectable /// [crate-level documentation]: crate #[diagnostic::on_unimplemented( - message = "`{Self}` does not provide type registration information", + message = "`{Self}` does not implement `GetTypeRegistration` so cannot provide type registration information", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait GetTypeRegistration: 'static { diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index a9f33711ae..6eafc3e7c4 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -171,7 +171,7 @@ impl Default for NonGenericTypeCell { /// # fn reflect_owned(self: Box) -> ReflectOwned { todo!() } /// # fn clone_value(&self) -> Box { todo!() } /// # } -/// # impl Reflect for Foo { +/// # impl Reflect for Foo { /// # fn into_any(self: Box) -> Box { todo!() } /// # fn as_any(&self) -> &dyn Any { todo!() } /// # fn as_any_mut(&mut self) -> &mut dyn Any { todo!() } diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 9e4a4ab9fd..6de62672a6 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -366,7 +366,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { if let Some(handle) = handle { let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?; - let Some(sample_type) = image.texture_format.sample_type(None, None) else { + let Some(sample_type) = image.texture_format.sample_type(None, Some(render_device.features())) else { return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType( #binding_index, "None".to_string(), diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 7d5ce067f5..bd515c0840 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -3,14 +3,13 @@ use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::Entity, + entity::{Entity, EntityHashMap}, query::{Has, With}, schedule::IntoSystemConfigs as _, system::{Query, Res, ResMut, Resource, StaticSystemParam}, world::{FromWorld, World}, }; use bevy_encase_derive::ShaderType; -use bevy_utils::EntityHashMap; use bytemuck::{Pod, Zeroable}; use nonmax::NonMaxU32; use smallvec::smallvec; @@ -99,7 +98,7 @@ where /// corresponds to each instance. /// /// This is keyed off each view. Each view has a separate buffer. - pub work_item_buffers: EntityHashMap, + pub work_item_buffers: EntityHashMap, /// The uniform data inputs for the current frame. /// diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 426450be8a..654254fd9b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -24,7 +24,7 @@ use bevy_ecs::{ reflect::ReflectComponent, system::{Commands, Query, Res, ResMut, Resource}, }; -use bevy_math::{vec2, Dir3, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3}; +use bevy_math::{ops, vec2, Dir3, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3}; use bevy_reflect::prelude::*; use bevy_render_macros::ExtractComponent; use bevy_transform::components::GlobalTransform; @@ -136,7 +136,7 @@ impl Exposure { /// #[inline] pub fn exposure(&self) -> f32 { - (-self.ev100).exp2() / 1.2 + ops::exp2(-self.ev100) / 1.2 } } @@ -170,9 +170,10 @@ pub struct PhysicalCameraParameters { impl PhysicalCameraParameters { /// Calculate the [EV100](https://en.wikipedia.org/wiki/Exposure_value). pub fn ev100(&self) -> f32 { - (self.aperture_f_stops * self.aperture_f_stops * 100.0 - / (self.shutter_speed_s * self.sensitivity_iso)) - .log2() + ops::log2( + self.aperture_f_stops * self.aperture_f_stops * 100.0 + / (self.shutter_speed_s * self.sensitivity_iso), + ) } } @@ -226,7 +227,7 @@ pub enum ViewportConversionError { /// Adding a camera is typically done by adding a bundle, either the `Camera2dBundle` or the /// `Camera3dBundle`. #[derive(Component, Debug, Reflect, Clone)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Camera { /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`]. pub viewport: Option, diff --git a/crates/bevy_render/src/camera/clear_color.rs b/crates/bevy_render/src/camera/clear_color.rs index 63f291aff4..e1cb54a39e 100644 --- a/crates/bevy_render/src/camera/clear_color.rs +++ b/crates/bevy_render/src/camera/clear_color.rs @@ -31,7 +31,7 @@ impl From for ClearColorConfig { /// This color appears as the "background" color for simple apps, /// when there are portions of the screen with nothing rendered. #[derive(Resource, Clone, Debug, Deref, DerefMut, ExtractResource, Reflect)] -#[reflect(Resource, Default)] +#[reflect(Resource, Default, Debug)] pub struct ClearColor(pub Color); /// Match the dark gray bevy website code block color by default. diff --git a/crates/bevy_render/src/camera/manual_texture_view.rs b/crates/bevy_render/src/camera/manual_texture_view.rs index b843f0ecaf..1672bcf399 100644 --- a/crates/bevy_render/src/camera/manual_texture_view.rs +++ b/crates/bevy_render/src/camera/manual_texture_view.rs @@ -10,7 +10,7 @@ use wgpu::TextureFormat; /// A unique id that corresponds to a specific [`ManualTextureView`] in the [`ManualTextureViews`] collection. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Component, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq, Hash)] pub struct ManualTextureViewHandle(pub u32); /// A manually managed [`TextureView`] for use as a [`crate::camera::RenderTarget`]. diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 0a6c3ca00a..3a08506f37 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -5,7 +5,7 @@ use crate::primitives::Frustum; use crate::view::VisibilitySystems; use bevy_app::{App, Plugin, PostStartup, PostUpdate}; use bevy_ecs::prelude::*; -use bevy_math::{AspectRatio, Mat4, Rect, Vec2, Vec3A}; +use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A}; use bevy_reflect::{ std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize, }; @@ -98,7 +98,7 @@ pub trait CameraProjection { /// A configurable [`CameraProjection`] that can select its projection type at runtime. #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub enum Projection { Perspective(PerspectiveProjection), Orthographic(OrthographicProjection), @@ -154,7 +154,7 @@ impl Default for Projection { /// A 3D camera projection in which distant objects appear smaller than close objects. #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct PerspectiveProjection { /// The vertical field of view (FOV) in radians. /// @@ -190,7 +190,9 @@ impl CameraProjection for PerspectiveProjection { } fn update(&mut self, width: f32, height: f32) { - self.aspect_ratio = AspectRatio::new(width, height).into(); + self.aspect_ratio = AspectRatio::try_new(width, height) + .expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values") + .ratio(); } fn far(&self) -> f32 { @@ -198,7 +200,7 @@ impl CameraProjection for PerspectiveProjection { } fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] { - let tan_half_fov = (self.fov / 2.).tan(); + let tan_half_fov = ops::tan(self.fov / 2.); let a = z_near.abs() * tan_half_fov; let b = z_far.abs() * tan_half_fov; let aspect_ratio = self.aspect_ratio; @@ -237,7 +239,7 @@ impl Default for PerspectiveProjection { /// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode}; /// let projection = Projection::Orthographic(OrthographicProjection { /// scaling_mode: ScalingMode::FixedVertical(2.0), -/// ..OrthographicProjection::default() +/// ..OrthographicProjection::default_2d() /// }); /// ``` #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] @@ -334,11 +336,11 @@ impl DivAssign for ScalingMode { /// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode}; /// let projection = Projection::Orthographic(OrthographicProjection { /// scaling_mode: ScalingMode::WindowSize(100.0), -/// ..OrthographicProjection::default() +/// ..OrthographicProjection::default_2d() /// }); /// ``` #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Debug, FromWorld)] pub struct OrthographicProjection { /// The distance of the near clipping plane in world units. /// @@ -369,17 +371,6 @@ pub struct OrthographicProjection { /// /// Defaults to `ScalingMode::WindowSize(1.0)` pub scaling_mode: ScalingMode, - /// Scales the projection. - /// - /// As scale increases, the apparent size of objects decreases, and vice versa. - /// - /// Note: scaling can be set by [`scaling_mode`](Self::scaling_mode) as well. - /// This parameter scales on top of that. - /// - /// This property is particularly useful in implementing zoom functionality. - /// - /// Defaults to `1.0`. - pub scale: f32, /// The area that the projection covers relative to `viewport_origin`. /// /// Bevy's [`camera_system`](crate::camera::camera_system) automatically @@ -452,10 +443,10 @@ impl CameraProjection for OrthographicProjection { } self.area = Rect::new( - self.scale * -origin_x, - self.scale * -origin_y, - self.scale * (projection_width - origin_x), - self.scale * (projection_height - origin_y), + -origin_x, + -origin_y, + projection_width - origin_x, + projection_height - origin_y, ); } @@ -479,10 +470,30 @@ impl CameraProjection for OrthographicProjection { } } -impl Default for OrthographicProjection { - fn default() -> Self { +impl FromWorld for OrthographicProjection { + fn from_world(_world: &mut World) -> Self { + OrthographicProjection::default_3d() + } +} + +impl OrthographicProjection { + /// Returns the default orthographic projection for a 2D context. + /// + /// The near plane is set to a negative value so that the camera can still + /// render the scene when using positive z coordinates to order foreground elements. + pub fn default_2d() -> Self { + OrthographicProjection { + near: -1000.0, + ..OrthographicProjection::default_3d() + } + } + + /// Returns the default orthographic projection for a 3D context. + /// + /// The near plane is set to 0.0 so that the camera doesn't render + /// objects that are behind it. + pub fn default_3d() -> Self { OrthographicProjection { - scale: 1.0, near: 0.0, far: 1000.0, viewport_origin: Vec2::new(0.5, 0.5), diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 3393d0ef44..ef1039920c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -447,7 +447,8 @@ fn extract(main_world: &mut World, render_world: &mut World) { main_world.insert_resource(ScratchMainWorld(scratch_world)); } -/// SAFETY: this function must be called from the main thread. +/// # Safety +/// This function must be called from the main thread. unsafe fn initialize_render_app(app: &mut App) { app.init_resource::(); diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 218e19c475..94186517a4 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -588,6 +588,12 @@ impl MeshAllocator { } for empty_slab in empty_slabs { + self.slab_layouts.values_mut().for_each(|slab_ids| { + let idx = slab_ids.iter().position(|&slab_id| slab_id == empty_slab); + if let Some(idx) = idx { + slab_ids.remove(idx); + } + }); self.slabs.remove(&empty_slab); } } diff --git a/crates/bevy_render/src/mesh/mesh/skinning.rs b/crates/bevy_render/src/mesh/mesh/skinning.rs index f1605ec74b..e7a91252e7 100644 --- a/crates/bevy_render/src/mesh/mesh/skinning.rs +++ b/crates/bevy_render/src/mesh/mesh/skinning.rs @@ -10,7 +10,7 @@ use bevy_reflect::prelude::*; use std::ops::Deref; #[derive(Component, Debug, Default, Clone, Reflect)] -#[reflect(Component, MapEntities, Default)] +#[reflect(Component, MapEntities, Default, Debug)] pub struct SkinnedMesh { pub inverse_bindposes: Handle, pub joints: Vec, diff --git a/crates/bevy_render/src/mesh/morph.rs b/crates/bevy_render/src/mesh/morph.rs index 8055d2975f..e44dd65b44 100644 --- a/crates/bevy_render/src/mesh/morph.rs +++ b/crates/bevy_render/src/mesh/morph.rs @@ -11,7 +11,7 @@ use bevy_hierarchy::Children; use bevy_math::Vec3; use bevy_reflect::prelude::*; use bytemuck::{Pod, Zeroable}; -use std::{iter, mem::size_of}; +use std::iter; use thiserror::Error; const MAX_TEXTURE_WIDTH: u32 = 2048; diff --git a/crates/bevy_render/src/mesh/primitives/dim2.rs b/crates/bevy_render/src/mesh/primitives/dim2.rs index 3b2d42b6c9..a7183f99f2 100644 --- a/crates/bevy_render/src/mesh/primitives/dim2.rs +++ b/crates/bevy_render/src/mesh/primitives/dim2.rs @@ -7,6 +7,7 @@ use crate::{ use super::{Extrudable, MeshBuilder, Meshable}; use bevy_math::{ + ops, primitives::{ Annulus, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Rectangle, RegularPolygon, Rhombus, Triangle2d, Triangle3d, WindingOrder, @@ -217,7 +218,7 @@ impl MeshBuilder for CircularSectorMeshBuilder { impl Extrudable for CircularSectorMeshBuilder { fn perimeter(&self) -> Vec { - let (sin, cos) = self.sector.arc.half_angle.sin_cos(); + let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle); let first_normal = Vec2::new(sin, cos); let last_normal = Vec2::new(-sin, cos); vec![ @@ -363,7 +364,7 @@ impl MeshBuilder for CircularSegmentMeshBuilder { impl Extrudable for CircularSegmentMeshBuilder { fn perimeter(&self) -> Vec { - let (sin, cos) = self.segment.arc.half_angle.sin_cos(); + let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle); let first_normal = Vec2::new(sin, cos); let last_normal = Vec2::new(-sin, cos); vec![ @@ -493,7 +494,7 @@ impl MeshBuilder for EllipseMeshBuilder { for i in 0..self.resolution { // Compute vertex position at angle theta let theta = start_angle + i as f32 * step; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); let x = cos * self.ellipse.half_size.x; let y = sin * self.ellipse.half_size.y; @@ -599,7 +600,7 @@ impl MeshBuilder for AnnulusMeshBuilder { let step = std::f32::consts::TAU / self.resolution as f32; for i in 0..=self.resolution { let theta = start_angle + (i % self.resolution) as f32 * step; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); let inner_pos = [cos * inner_radius, sin * inner_radius, 0.]; let outer_pos = [cos * outer_radius, sin * outer_radius, 0.]; positions.push(inner_pos); @@ -915,7 +916,7 @@ impl MeshBuilder for Capsule2dMeshBuilder { for i in 0..resolution { // Compute vertex position at angle theta let theta = start_angle + i as f32 * step; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); let (x, y) = (cos * radius, sin * radius + self.capsule.half_length); positions.push([x, y, 0.0]); @@ -934,7 +935,7 @@ impl MeshBuilder for Capsule2dMeshBuilder { for i in resolution..vertex_count { // Compute vertex position at angle theta let theta = start_angle + i as f32 * step; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); let (x, y) = (cos * radius, sin * radius - self.capsule.half_length); positions.push([x, y, 0.0]); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs b/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs index 3c85fc629b..eeea5e906f 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs @@ -2,7 +2,7 @@ use crate::{ mesh::{Indices, Mesh, MeshBuilder, Meshable}, render_asset::RenderAssetUsages, }; -use bevy_math::{primitives::Capsule3d, Vec2, Vec3}; +use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3}; use wgpu::PrimitiveTopology; /// Manner in which UV coordinates are distributed vertically. @@ -158,11 +158,8 @@ impl MeshBuilder for Capsule3dMeshBuilder { let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal); let theta = jf * to_theta; - let cos_theta = theta.cos(); - let sin_theta = theta.sin(); - - theta_cartesian[j] = Vec2::new(cos_theta, sin_theta); - rho_theta_cartesian[j] = Vec2::new(radius * cos_theta, radius * sin_theta); + theta_cartesian[j] = Vec2::from_angle(theta); + rho_theta_cartesian[j] = radius * theta_cartesian[j]; // North. vs[j] = Vec3::new(0.0, summit, 0.0); @@ -205,8 +202,7 @@ impl MeshBuilder for Capsule3dMeshBuilder { let phi = ip1f * to_phi; // For coordinates. - let cos_phi_south = phi.cos(); - let sin_phi_south = phi.sin(); + let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi); // Symmetrical hemispheres mean cosine and sine only needs // to be calculated once. diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs b/crates/bevy_render/src/mesh/primitives/dim3/cone.rs index fb201bd5ef..7ad5394a3c 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/cone.rs @@ -1,4 +1,4 @@ -use bevy_math::{primitives::Cone, Vec3}; +use bevy_math::{ops, primitives::Cone, Vec3}; use wgpu::PrimitiveTopology; use crate::{ @@ -116,7 +116,7 @@ impl MeshBuilder for ConeMeshBuilder { // Add vertices for the bottom of the lateral surface. for segment in 0..self.resolution { let theta = segment as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); // The vertex normal perpendicular to the side let normal = Vec3::new(cos, normal_slope, sin) * normalization_factor; @@ -142,7 +142,7 @@ impl MeshBuilder for ConeMeshBuilder { // Add base vertices. for i in 0..self.resolution { let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); positions.push([cos * self.cone.radius, -half_height, sin * self.cone.radius]); normals.push([0.0, -1.0, 0.0]); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs b/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs index a7d09778a9..3b7da0bea5 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs @@ -2,7 +2,7 @@ use crate::{ mesh::{Indices, Mesh, MeshBuilder, Meshable}, render_asset::RenderAssetUsages, }; -use bevy_math::{primitives::ConicalFrustum, Vec3}; +use bevy_math::{ops, primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; /// A builder used for creating a [`Mesh`] with a [`ConicalFrustum`] shape. @@ -94,7 +94,7 @@ impl MeshBuilder for ConicalFrustumMeshBuilder { for segment in 0..=self.resolution { let theta = segment as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); positions.push([radius * cos, y, radius * sin]); normals.push( @@ -137,7 +137,7 @@ impl MeshBuilder for ConicalFrustumMeshBuilder { for i in 0..self.resolution { let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); positions.push([cos * radius, y, sin * radius]); normals.push([0.0, normal_y, 0.0]); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs b/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs index 4b53f9dd34..c6522d98c7 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs @@ -1,4 +1,4 @@ -use bevy_math::primitives::Cylinder; +use bevy_math::{ops, primitives::Cylinder}; use wgpu::PrimitiveTopology; use crate::{ @@ -122,7 +122,7 @@ impl MeshBuilder for CylinderMeshBuilder { for segment in 0..=resolution { let theta = segment as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]); normals.push([cos, 0., sin]); @@ -163,7 +163,7 @@ impl MeshBuilder for CylinderMeshBuilder { for i in 0..self.resolution { let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); + let (sin, cos) = ops::sin_cos(theta); positions.push([cos * self.cylinder.radius, y, sin * self.cylinder.radius]); normals.push([0.0, normal_y, 0.0]); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs b/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs index 883afe16b1..0d2e4e84d8 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs @@ -4,7 +4,7 @@ use crate::{ mesh::{Indices, Mesh, MeshBuilder, Meshable}, render_asset::RenderAssetUsages, }; -use bevy_math::primitives::Sphere; +use bevy_math::{ops, primitives::Sphere}; use hexasphere::shapes::IcoSphere; use thiserror::Error; use wgpu::PrimitiveTopology; @@ -120,8 +120,8 @@ impl SphereMeshBuilder { }); } let generated = IcoSphere::new(subdivisions as usize, |point| { - let inclination = point.y.acos(); - let azimuth = point.z.atan2(point.x); + let inclination = ops::acos(point.y); + let azimuth = ops::atan2(point.z, point.x); let norm_inclination = inclination / PI; let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU); @@ -183,13 +183,13 @@ impl SphereMeshBuilder { for i in 0..stacks + 1 { let stack_angle = PI / 2. - (i as f32) * stack_step; - let xy = self.sphere.radius * stack_angle.cos(); - let z = self.sphere.radius * stack_angle.sin(); + let xy = self.sphere.radius * ops::cos(stack_angle); + let z = self.sphere.radius * ops::sin(stack_angle); for j in 0..sectors + 1 { let sector_angle = (j as f32) * sector_step; - let x = xy * sector_angle.cos(); - let y = xy * sector_angle.sin(); + let x = xy * ops::cos(sector_angle); + let y = xy * ops::sin(sector_angle); vertices.push([x, y, z]); normals.push([x * length_inv, y * length_inv, z * length_inv]); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/torus.rs b/crates/bevy_render/src/mesh/primitives/dim3/torus.rs index ec45f5f382..0dcbd68353 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/torus.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/torus.rs @@ -1,4 +1,4 @@ -use bevy_math::{primitives::Torus, Vec3}; +use bevy_math::{ops, primitives::Torus, Vec3}; use std::ops::RangeInclusive; use wgpu::PrimitiveTopology; @@ -98,17 +98,20 @@ impl MeshBuilder for TorusMeshBuilder { for side in 0..=self.minor_resolution { let phi = side_stride * side as f32; + let (sin_theta, cos_theta) = ops::sin_cos(theta); + let (sin_phi, cos_phi) = ops::sin_cos(phi); + let radius = self.torus.major_radius + self.torus.minor_radius * cos_phi; let position = Vec3::new( - theta.cos() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), - self.torus.minor_radius * phi.sin(), - theta.sin() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), + cos_theta * radius, + self.torus.minor_radius * sin_phi, + sin_theta * radius, ); let center = Vec3::new( - self.torus.major_radius * theta.cos(), + self.torus.major_radius * cos_theta, 0., - self.torus.major_radius * theta.sin(), + self.torus.major_radius * sin_theta, ); let normal = (position - center).normalize(); diff --git a/crates/bevy_render/src/primitives/mod.rs b/crates/bevy_render/src/primitives/mod.rs index 921a2cbc44..476e4740ec 100644 --- a/crates/bevy_render/src/primitives/mod.rs +++ b/crates/bevy_render/src/primitives/mod.rs @@ -31,7 +31,7 @@ use bevy_reflect::prelude::*; /// [`Mesh`]: crate::mesh::Mesh /// [`Handle`]: crate::mesh::Mesh #[derive(Component, Clone, Copy, Debug, Default, Reflect, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Aabb { pub center: Vec3A, pub half_extents: Vec3A, @@ -212,7 +212,7 @@ impl HalfSpace { /// [`CameraProjection`]: crate::camera::CameraProjection /// [`GlobalTransform`]: bevy_transform::components::GlobalTransform #[derive(Component, Clone, Copy, Debug, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Frustum { #[reflect(ignore)] pub half_spaces: [HalfSpace; 6], @@ -303,7 +303,7 @@ impl Frustum { } #[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct CubemapFrusta { #[reflect(ignore)] pub frusta: [Frustum; 6], @@ -319,7 +319,7 @@ impl CubemapFrusta { } #[derive(Component, Debug, Default, Reflect, Clone)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct CascadesFrusta { #[reflect(ignore)] pub frusta: EntityHashMap>, diff --git a/crates/bevy_render/src/render_phase/draw_state.rs b/crates/bevy_render/src/render_phase/draw_state.rs index 0f682655c3..f19dff1971 100644 --- a/crates/bevy_render/src/render_phase/draw_state.rs +++ b/crates/bevy_render/src/render_phase/draw_state.rs @@ -24,6 +24,9 @@ struct DrawState { /// List of vertex buffers by [`BufferId`], offset, and size. See [`DrawState::buffer_slice_key`] vertex_buffers: Vec>, index_buffer: Option<(BufferId, u64, IndexFormat)>, + + /// Stores whether this state is populated or empty for quick state invalidation + stores_state: bool, } impl DrawState { @@ -34,6 +37,7 @@ impl DrawState { // self.vertex_buffers.clear(); // self.index_buffer = None; self.pipeline = Some(pipeline); + self.stores_state = true; } /// Checks, whether the `pipeline` is already bound. @@ -47,6 +51,7 @@ impl DrawState { group.0 = Some(bind_group); group.1.clear(); group.1.extend(dynamic_indices); + self.stores_state = true; } /// Checks, whether the `bind_group` is already bound to the `index`. @@ -66,6 +71,7 @@ impl DrawState { /// Marks the vertex `buffer` as bound to the `index`. fn set_vertex_buffer(&mut self, index: usize, buffer_slice: BufferSlice) { self.vertex_buffers[index] = Some(self.buffer_slice_key(&buffer_slice)); + self.stores_state = true; } /// Checks, whether the vertex `buffer` is already bound to the `index`. @@ -89,6 +95,7 @@ impl DrawState { /// Marks the index `buffer` as bound. fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { self.index_buffer = Some((buffer, offset, index_format)); + self.stores_state = true; } /// Checks, whether the index `buffer` is already bound. @@ -100,6 +107,23 @@ impl DrawState { ) -> bool { self.index_buffer == Some((buffer, offset, index_format)) } + + /// Resets tracking state + pub fn reset_tracking(&mut self) { + if !self.stores_state { + return; + } + self.pipeline = None; + self.bind_groups.iter_mut().for_each(|val| { + val.0 = None; + val.1.clear(); + }); + self.vertex_buffers.iter_mut().for_each(|val| { + *val = None; + }); + self.index_buffer = None; + self.stores_state = false; + } } /// A [`RenderPass`], which tracks the current pipeline state to skip redundant operations. @@ -128,7 +152,11 @@ impl<'a> TrackedRenderPass<'a> { } /// Returns the wgpu [`RenderPass`]. + /// + /// Function invalidates internal tracking state, + /// some redundant pipeline operations may not be skipped. pub fn wgpu_pass(&mut self) -> &mut RenderPass<'a> { + self.state.reset_tracking(); &mut self.pass } diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index c50cf0583c..c720605d92 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -1,4 +1,4 @@ -use std::{iter, marker::PhantomData, mem::size_of}; +use std::{iter, marker::PhantomData}; use crate::{ render_resource::Buffer, @@ -123,7 +123,7 @@ impl RawBufferVec { } /// Creates a [`Buffer`] on the [`RenderDevice`] with size - /// at least `std::mem::size_of::() * capacity`, unless a such a buffer already exists. + /// at least `size_of::() * capacity`, unless a such a buffer already exists. /// /// If a [`Buffer`] exists, but is too small, references to it will be discarded, /// and a new [`Buffer`] will be created. Any previously created [`Buffer`]s @@ -302,7 +302,7 @@ where } /// Creates a [`Buffer`] on the [`RenderDevice`] with size - /// at least `std::mem::size_of::() * capacity`, unless such a buffer already exists. + /// at least `size_of::() * capacity`, unless such a buffer already exists. /// /// If a [`Buffer`] exists, but is too small, references to it will be discarded, /// and a new [`Buffer`] will be created. Any previously created [`Buffer`]s diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index 4225ee7e28..4c6baed318 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -8,6 +8,8 @@ use bevy_ecs::system::SystemParamItem; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_utils::default; +use encase::internal::WriteInto; +use encase::ShaderType; use wgpu::util::BufferInitDescriptor; /// Adds [`ShaderStorageBuffer`] as an asset that is extracted and uploaded to the GPU. @@ -72,6 +74,29 @@ impl ShaderStorageBuffer { storage.asset_usage = asset_usage; storage } + + /// Sets the data of the storage buffer to the given [`ShaderType`]. + pub fn set_data(&mut self, value: T) + where + T: ShaderType + WriteInto, + { + let size = value.size().get() as usize; + let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); + wrapper.write(&value).unwrap(); + self.data = Some(wrapper.into_inner()); + } +} + +impl From for ShaderStorageBuffer +where + T: ShaderType + WriteInto, +{ + fn from(value: T) -> Self { + let size = value.size().get() as usize; + let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); + wrapper.write(&value).unwrap(); + Self::new(wrapper.as_ref(), RenderAssetUsages::default()) + } } /// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 8d92395b63..55b1560a90 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -666,7 +666,9 @@ impl Image { /// Returns the aspect ratio (width / height) of a 2D image. #[inline] pub fn aspect_ratio(&self) -> AspectRatio { - AspectRatio::from_pixels(self.width(), self.height()) + AspectRatio::try_from_pixels(self.width(), self.height()).expect( + "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values", + ) } /// Returns the size of a 2D image as f32. diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 9b1a592773..566579d7c8 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -107,11 +107,16 @@ impl Plugin for ImagePlugin { .world() .get_resource::() { - processor.register_processor::>( - CompressedImageSaver.into(), - ); - processor - .set_default_processor::>("png"); + processor.register_processor::, + CompressedImageSaver, + >>(CompressedImageSaver.into()); + processor.set_default_processor::, + CompressedImageSaver, + >>("png"); } if let Some(render_app) = app.get_sub_app_mut(RenderApp) { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index d4392c8e7c..6542677c94 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -166,7 +166,7 @@ impl Plugin for ViewPlugin { Hash, Debug, )] -#[reflect(Component, Default)] +#[reflect(Component, Default, PartialEq, Hash, Debug)] pub enum Msaa { Off = 1, Sample2 = 2, @@ -210,7 +210,7 @@ impl ExtractedView { /// `post_saturation` value in [`ColorGradingGlobal`], which is applied after /// tonemapping. #[derive(Component, Reflect, Debug, Default, Clone)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct ColorGrading { /// Filmic color grading values applied to the image as a whole (as opposed /// to individual sections, like shadows and highlights). diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 8181d3c911..b219b89f12 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -31,7 +31,7 @@ use super::NoCpuCulling; /// This is done by the `visibility_propagate_system` which uses the entity hierarchy and /// `Visibility` to set the values of each entity's [`InheritedVisibility`] component. #[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub enum Visibility { /// An entity with `Visibility::Inherited` will inherit the Visibility of its [`Parent`]. /// @@ -105,7 +105,7 @@ impl PartialEq<&Visibility> for Visibility { /// /// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate #[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct InheritedVisibility(bool); impl InheritedVisibility { @@ -133,7 +133,7 @@ impl InheritedVisibility { /// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate /// [`CheckVisibility`]: VisibilitySystems::CheckVisibility #[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct ViewVisibility(bool); impl ViewVisibility { @@ -190,7 +190,7 @@ pub struct VisibilityBundle { /// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`] /// to appear in the reflection of a [`Mesh`] within. #[derive(Debug, Component, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct NoFrustumCulling; /// Collection of entities visible from the current view. @@ -203,7 +203,7 @@ pub struct NoFrustumCulling; /// This component is intended to be attached to the same entity as the [`Camera`] and /// the [`Frustum`] defining the view. #[derive(Clone, Component, Default, Debug, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct VisibleEntities { #[reflect(ignore)] pub entities: TypeIdMap>, @@ -537,7 +537,6 @@ mod test { use super::*; use bevy_app::prelude::*; use bevy_hierarchy::BuildChildren; - use std::mem::size_of; fn visibility_bundle(visibility: Visibility) -> VisibilityBundle { VisibilityBundle { @@ -565,13 +564,13 @@ mod test { app.world_mut() .entity_mut(root1) - .push_children(&[root1_child1, root1_child2]); + .add_children(&[root1_child1, root1_child2]); app.world_mut() .entity_mut(root1_child1) - .push_children(&[root1_child1_grandchild1]); + .add_children(&[root1_child1_grandchild1]); app.world_mut() .entity_mut(root1_child2) - .push_children(&[root1_child2_grandchild1]); + .add_children(&[root1_child2_grandchild1]); let root2 = app.world_mut().spawn(VisibilityBundle::default()).id(); let root2_child1 = app.world_mut().spawn(VisibilityBundle::default()).id(); @@ -584,13 +583,13 @@ mod test { app.world_mut() .entity_mut(root2) - .push_children(&[root2_child1, root2_child2]); + .add_children(&[root2_child1, root2_child2]); app.world_mut() .entity_mut(root2_child1) - .push_children(&[root2_child1_grandchild1]); + .add_children(&[root2_child1_grandchild1]); app.world_mut() .entity_mut(root2_child2) - .push_children(&[root2_child2_grandchild1]); + .add_children(&[root2_child2_grandchild1]); app.update(); @@ -662,13 +661,13 @@ mod test { app.world_mut() .entity_mut(root1) - .push_children(&[root1_child1, root1_child2]); + .add_children(&[root1_child1, root1_child2]); app.world_mut() .entity_mut(root1_child1) - .push_children(&[root1_child1_grandchild1]); + .add_children(&[root1_child1_grandchild1]); app.world_mut() .entity_mut(root1_child2) - .push_children(&[root1_child2_grandchild1]); + .add_children(&[root1_child2_grandchild1]); app.update(); @@ -714,13 +713,13 @@ mod test { let id1 = world.spawn(VisibilityBundle::default()).id(); let id2 = world.spawn(VisibilityBundle::default()).id(); - world.entity_mut(id1).push_children(&[id2]); + world.entity_mut(id1).add_children(&[id2]); let id3 = world.spawn(visibility_bundle(Visibility::Hidden)).id(); - world.entity_mut(id2).push_children(&[id3]); + world.entity_mut(id2).add_children(&[id3]); let id4 = world.spawn(VisibilityBundle::default()).id(); - world.entity_mut(id3).push_children(&[id4]); + world.entity_mut(id3).add_children(&[id4]); // Test the hierarchy. @@ -787,7 +786,7 @@ mod test { let parent = world.spawn(()).id(); let child = world.spawn(VisibilityBundle::default()).id(); - world.entity_mut(parent).push_children(&[child]); + world.entity_mut(parent).add_children(&[child]); schedule.run(&mut world); world.clear_trackers(); diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index d1e1d6546f..48ba4df5e4 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -9,15 +9,16 @@ use std::{ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ component::Component, - entity::Entity, + entity::{Entity, EntityHashMap}, query::{Changed, With}, + reflect::ReflectComponent, schedule::IntoSystemConfigs as _, system::{Query, Res, ResMut, Resource}, }; use bevy_math::{vec4, FloatOrd, Vec4}; use bevy_reflect::Reflect; use bevy_transform::components::GlobalTransform; -use bevy_utils::{prelude::default, EntityHashMap, HashMap}; +use bevy_utils::{prelude::default, HashMap}; use nonmax::NonMaxU16; use wgpu::{BufferBindingType, BufferUsages}; @@ -109,6 +110,7 @@ impl Plugin for VisibilityRangePlugin { /// `start_margin` of the next lower LOD; this is important for the crossfade /// effect to function properly. #[derive(Component, Clone, PartialEq, Reflect)] +#[reflect(Component, PartialEq, Hash)] pub struct VisibilityRange { /// The range of distances, in world units, between which this entity will /// smoothly fade into view as the camera zooms out. @@ -191,7 +193,7 @@ impl VisibilityRange { #[derive(Resource)] pub struct RenderVisibilityRanges { /// Information corresponding to each entity. - entities: EntityHashMap, + entities: EntityHashMap, /// Maps a [`VisibilityRange`] to its index within the `buffer`. /// @@ -309,13 +311,13 @@ impl RenderVisibilityRanges { #[derive(Resource, Default)] pub struct VisibleEntityRanges { /// Stores which bit index each view corresponds to. - views: EntityHashMap, + views: EntityHashMap, /// Stores a bitmask in which each view has a single bit. /// /// A 0 bit for a view corresponds to "out of range"; a 1 bit corresponds to /// "in range". - entities: EntityHashMap, + entities: EntityHashMap, } impl VisibleEntityRanges { diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index 63ba1fdf80..81a1df0c2f 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -21,7 +21,7 @@ pub type Layer = usize; /// /// Entities without this component belong to layer `0`. #[derive(Component, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default, PartialEq, Debug)] pub struct RenderLayers(SmallVec<[u64; INLINE_BLOCKS]>); /// The number of memory blocks stored inline diff --git a/crates/bevy_render/src/view/window/cursor.rs b/crates/bevy_render/src/view/window/cursor.rs index 3bed223631..df825ecb6c 100644 --- a/crates/bevy_render/src/view/window/cursor.rs +++ b/crates/bevy_render/src/view/window/cursor.rs @@ -4,10 +4,11 @@ use bevy_ecs::{ change_detection::DetectChanges, component::Component, entity::Entity, + observer::Trigger, query::With, reflect::ReflectComponent, system::{Commands, Local, Query, Res}, - world::Ref, + world::{OnRemove, Ref}, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_utils::{tracing::warn, HashSet}; @@ -27,12 +28,14 @@ impl Plugin for CursorPlugin { app.register_type::() .init_resource::() .add_systems(Last, update_cursors); + + app.observe(on_remove_cursor_icon); } } /// Insert into a window entity to set the cursor for that window. #[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)] -#[reflect(Component, Debug, Default)] +#[reflect(Component, Debug, Default, PartialEq)] pub enum CursorIcon { /// Custom cursor image. Custom(CustomCursor), @@ -84,12 +87,12 @@ pub enum CustomCursor { pub fn update_cursors( mut commands: Commands, - mut windows: Query<(Entity, Ref), With>, + windows: Query<(Entity, Ref), With>, cursor_cache: Res, images: Res>, mut queue: Local>, ) { - for (entity, cursor) in windows.iter_mut() { + for (entity, cursor) in windows.iter() { if !(queue.remove(&entity) || cursor.is_changed()) { continue; } @@ -161,6 +164,15 @@ pub fn update_cursors( } } +/// Resets the cursor to the default icon when `CursorIcon` is removed. +pub fn on_remove_cursor_icon(trigger: Trigger, mut commands: Commands) { + commands + .entity(trigger.entity()) + .insert(PendingCursor(Some(CursorSource::System( + convert_system_cursor_icon(SystemCursorIcon::Default), + )))); +} + /// Returns the image data as a `Vec`. /// Only supports rgba8 and rgba32float formats. fn image_to_rgba_pixels(image: &Image) -> Option> { diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 6667fe836d..411d5a6099 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -213,7 +213,7 @@ mod tests { use bevy_ecs::reflect::{ReflectMapEntitiesResource, ReflectResource}; use bevy_ecs::system::Resource; use bevy_ecs::{reflect::AppTypeRegistry, world::Command, world::World}; - use bevy_hierarchy::{Parent, PushChild}; + use bevy_hierarchy::{AddChild, Parent}; use bevy_reflect::Reflect; use crate::dynamic_scene_builder::DynamicSceneBuilder; @@ -284,7 +284,7 @@ mod tests { .register::(); let original_parent_entity = world.spawn_empty().id(); let original_child_entity = world.spawn_empty().id(); - PushChild { + AddChild { parent: original_parent_entity, child: original_child_entity, } @@ -305,7 +305,7 @@ mod tests { // We then add the parent from the scene as a child of the original child // Hierarchy should look like: // Original Parent <- Original Child <- Scene Parent <- Scene Child - PushChild { + AddChild { parent: original_child_entity, child: from_scene_parent_entity, } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 26084c1a3e..e7d4519687 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, world::World, }; -use bevy_reflect::PartialReflect; +use bevy_reflect::{PartialReflect, ReflectFromReflect}; use bevy_utils::default; use std::collections::BTreeMap; @@ -16,8 +16,8 @@ use std::collections::BTreeMap; /// /// By default, all components registered with [`ReflectComponent`] type data in a world's [`AppTypeRegistry`] will be extracted. /// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Component)]` attribute). -/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_filter) or by explicitly -/// [allowing](DynamicSceneBuilder::allow)/[denying](DynamicSceneBuilder::deny) certain components. +/// This can be changed by [specifying a filter](DynamicSceneBuilder::with_component_filter) or by explicitly +/// [allowing](DynamicSceneBuilder::allow_component)/[denying](DynamicSceneBuilder::deny_component) certain components. /// /// Extraction happens immediately and uses the filter as it exists during the time of extraction. /// @@ -76,7 +76,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// Specify a custom component [`SceneFilter`] to be used with this builder. #[must_use] - pub fn with_filter(mut self, filter: SceneFilter) -> Self { + pub fn with_component_filter(mut self, filter: SceneFilter) -> Self { self.component_filter = filter; self } @@ -88,14 +88,34 @@ impl<'w> DynamicSceneBuilder<'w> { self } + /// Updates the filter to allow all component and resource types. + /// + /// This is useful for resetting the filter so that types may be selectively denied + /// with [`deny_component`](`Self::deny_component`) and [`deny_resource`](`Self::deny_resource`). + pub fn allow_all(mut self) -> Self { + self.component_filter = SceneFilter::allow_all(); + self.resource_filter = SceneFilter::allow_all(); + self + } + + /// Updates the filter to deny all component and resource types. + /// + /// This is useful for resetting the filter so that types may be selectively allowed + /// with [`allow_component`](`Self::allow_component`) and [`allow_resource`](`Self::allow_resource`). + pub fn deny_all(mut self) -> Self { + self.component_filter = SceneFilter::deny_all(); + self.resource_filter = SceneFilter::deny_all(); + self + } + /// Allows the given component type, `T`, to be included in the generated scene. /// /// This method may be called multiple times for any number of components. /// - /// This is the inverse of [`deny`](Self::deny). + /// This is the inverse of [`deny_component`](Self::deny_component). /// If `T` has already been denied, then it will be removed from the denylist. #[must_use] - pub fn allow(mut self) -> Self { + pub fn allow_component(mut self) -> Self { self.component_filter = self.component_filter.allow::(); self } @@ -104,10 +124,10 @@ impl<'w> DynamicSceneBuilder<'w> { /// /// This method may be called multiple times for any number of components. /// - /// This is the inverse of [`allow`](Self::allow). + /// This is the inverse of [`allow_component`](Self::allow_component). /// If `T` has already been allowed, then it will be removed from the allowlist. #[must_use] - pub fn deny(mut self) -> Self { + pub fn deny_component(mut self) -> Self { self.component_filter = self.component_filter.deny::(); self } @@ -116,9 +136,9 @@ impl<'w> DynamicSceneBuilder<'w> { /// /// This is useful for resetting the filter so that types may be selectively [denied]. /// - /// [denied]: Self::deny + /// [denied]: Self::deny_component #[must_use] - pub fn allow_all(mut self) -> Self { + pub fn allow_all_components(mut self) -> Self { self.component_filter = SceneFilter::allow_all(); self } @@ -127,9 +147,9 @@ impl<'w> DynamicSceneBuilder<'w> { /// /// This is useful for resetting the filter so that types may be selectively [allowed]. /// - /// [allowed]: Self::allow + /// [allowed]: Self::allow_component #[must_use] - pub fn deny_all(mut self) -> Self { + pub fn deny_all_components(mut self) -> Self { self.component_filter = SceneFilter::deny_all(); self } @@ -242,8 +262,8 @@ impl<'w> DynamicSceneBuilder<'w> { /// /// Note that components extracted from queried entities must still pass through the filter if one is set. /// - /// [`allow`]: Self::allow - /// [`deny`]: Self::deny + /// [`allow`]: Self::allow_component + /// [`deny`]: Self::deny_component #[must_use] pub fn extract_entities(mut self, entities: impl Iterator) -> Self { let type_registry = self.original_world.resource::().read(); @@ -274,11 +294,22 @@ impl<'w> DynamicSceneBuilder<'w> { return None; } - let component = type_registry - .get(type_id)? + let type_registration = type_registry.get(type_id)?; + + let component = type_registration .data::()? .reflect(original_entity)?; - entry.components.push(component.clone_value()); + + // Clone via `FromReflect`. Unlike `PartialReflect::clone_value` this + // retains the original type and `ReflectSerialize` type data which is needed to + // deserialize. + let component = type_registration + .data::() + .and_then(|fr| fr.from_reflect(component.as_partial_reflect())) + .map(PartialReflect::into_partial_reflect) + .unwrap_or_else(|| component.clone_value()); + + entry.components.push(component); Some(()) }; extract_and_push(); @@ -571,7 +602,7 @@ mod tests { let entity_b = world.spawn(ComponentB).id(); let scene = DynamicSceneBuilder::from_world(&world) - .allow::() + .allow_component::() .extract_entities([entity_a_b, entity_a, entity_b].into_iter()) .build(); @@ -598,7 +629,7 @@ mod tests { let entity_b = world.spawn(ComponentB).id(); let scene = DynamicSceneBuilder::from_world(&world) - .deny::() + .deny_component::() .extract_entities([entity_a_b, entity_a, entity_b].into_iter()) .build(); diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 9aa59bb6fd..fb3fa62466 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ system::Resource, world::{Command, Mut, World}, }; -use bevy_hierarchy::{BuildChildren, DespawnRecursiveExt, Parent, PushChild}; +use bevy_hierarchy::{AddChild, BuildChildren, DespawnRecursiveExt, Parent}; use bevy_utils::{tracing::error, HashMap, HashSet}; use thiserror::Error; use uuid::Uuid; @@ -380,7 +380,7 @@ impl SceneSpawner { // this case shouldn't happen anyway .unwrap_or(true) { - PushChild { + AddChild { parent, child: entity, } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 497c4c4afc..bbf5735018 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -3,11 +3,11 @@ use crate::{DynamicEntity, DynamicScene}; use bevy_ecs::entity::Entity; use bevy_reflect::serde::{TypedReflectDeserializer, TypedReflectSerializer}; -use bevy_reflect::PartialReflect; use bevy_reflect::{ serde::{ReflectDeserializer, TypeRegistrationDeserializer}, TypeRegistry, }; +use bevy_reflect::{PartialReflect, ReflectFromReflect}; use bevy_utils::HashSet; use serde::ser::SerializeMap; use serde::{ @@ -154,6 +154,8 @@ impl<'a> Serialize for EntitySerializer<'a> { /// Used to serialize scene resources in [`SceneSerializer`] and entity components in [`EntitySerializer`]. /// Note that having several entries of the same type in `entries` will lead to an error when using the RON format and /// deserializing through [`SceneMapDeserializer`]. +/// +/// Note: The entries are sorted by type path before they're serialized. pub struct SceneMapSerializer<'a> { /// List of boxed values of unique type to serialize. pub entries: &'a [Box], @@ -167,10 +169,25 @@ impl<'a> Serialize for SceneMapSerializer<'a> { S: Serializer, { let mut state = serializer.serialize_map(Some(self.entries.len()))?; - for reflect in self.entries { + let sorted_entries = { + let mut entries = self + .entries + .iter() + .map(|entry| { + ( + entry.get_represented_type_info().unwrap().type_path(), + entry.as_partial_reflect(), + ) + }) + .collect::>(); + entries.sort_by_key(|(type_path, _partial_reflect)| *type_path); + entries + }; + + for (type_path, partial_reflect) in sorted_entries { state.serialize_entry( - reflect.get_represented_type_info().unwrap().type_path(), - &TypedReflectSerializer::new(reflect.as_partial_reflect(), self.registry), + type_path, + &TypedReflectSerializer::new(partial_reflect, self.registry), )?; } state.end() @@ -471,9 +488,19 @@ impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> { ))); } - entries.push( - map.next_value_seed(TypedReflectDeserializer::new(registration, self.registry))?, - ); + let value = + map.next_value_seed(TypedReflectDeserializer::new(registration, self.registry))?; + + // Attempt to convert using FromReflect. + let value = self + .registry + .get(registration.type_id()) + .and_then(|tr| tr.data::()) + .and_then(|fr| fr.from_reflect(value.as_partial_reflect())) + .map(PartialReflect::into_partial_reflect) + .unwrap_or(value); + + entries.push(value); } Ok(entries) @@ -491,10 +518,10 @@ mod tests { use bevy_ecs::query::{With, Without}; use bevy_ecs::reflect::{AppTypeRegistry, ReflectMapEntities}; use bevy_ecs::world::FromWorld; - use bevy_reflect::{Reflect, ReflectSerialize}; + use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bincode::Options; use serde::de::DeserializeSeed; - use serde::Serialize; + use serde::{Deserialize, Serialize}; use std::io::BufReader; #[derive(Component, Reflect, Default)] @@ -507,6 +534,30 @@ mod tests { #[reflect(Component)] struct Baz(i32); + // De/serialize as hex. + mod qux { + use serde::{de::Error, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &u32, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{:X}", value)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + u32::from_str_radix(<&str as Deserialize>::deserialize(deserializer)?, 16) + .map_err(Error::custom) + } + } + + #[derive(Component, Copy, Clone, Reflect, Debug, PartialEq, Serialize, Deserialize)] + #[reflect(Component, Serialize, Deserialize)] + struct Qux(#[serde(with = "qux")] u32); + #[derive(Component, Reflect, Default)] #[reflect(Component)] struct MyComponent { @@ -555,6 +606,7 @@ mod tests { registry.register::(); registry.register::(); registry.register::(); + registry.register::(); registry.register::(); registry.register::(); registry.register::(); @@ -598,15 +650,15 @@ mod tests { ), 4294967297: ( components: { - "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), + "bevy_scene::serde::tests::Foo": (123), }, ), 4294967298: ( components: { - "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Baz": (789), + "bevy_scene::serde::tests::Foo": (123), }, ), }, @@ -679,6 +731,18 @@ mod tests { assert_eq!(1, dst_world.query::<&Baz>().iter(&dst_world).count()); } + fn roundtrip_ron(world: &World) -> (DynamicScene, DynamicScene) { + let scene = DynamicScene::from_world(world); + let registry = world.resource::().read(); + let serialized = scene.serialize(®istry).unwrap(); + let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); + let scene_deserializer = SceneDeserializer { + type_registry: ®istry, + }; + let deserialized_scene = scene_deserializer.deserialize(&mut deserializer).unwrap(); + (scene, deserialized_scene) + } + #[test] fn should_roundtrip_with_later_generations_and_obsolete_references() { let mut world = create_world(); @@ -690,19 +754,7 @@ mod tests { world.despawn(a); world.spawn(MyEntityRef(foo)).insert(Bar(123)); - let registry = world.resource::(); - - let scene = DynamicScene::from_world(&world); - - let serialized = scene - .serialize(&world.resource::().read()) - .unwrap(); - let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); - let scene_deserializer = SceneDeserializer { - type_registry: ®istry.0.read(), - }; - - let deserialized_scene = scene_deserializer.deserialize(&mut deserializer).unwrap(); + let (scene, deserialized_scene) = roundtrip_ron(&world); let mut map = EntityHashMap::default(); let mut dst_world = create_world(); @@ -730,6 +782,24 @@ mod tests { .all(|r| world.get_entity(r.0).is_none())); } + #[test] + fn should_roundtrip_with_custom_serialization() { + let mut world = create_world(); + let qux = Qux(42); + world.spawn(qux); + + let (scene, deserialized_scene) = roundtrip_ron(&world); + + assert_eq!(1, deserialized_scene.entities.len()); + assert_scene_eq(&scene, &deserialized_scene); + + let mut world = create_world(); + deserialized_scene + .write_to_world(&mut world, &mut EntityHashMap::default()) + .unwrap(); + assert_eq!(&qux, world.query::<&Qux>().single(&world)); + } + #[test] fn should_roundtrip_postcard() { let mut world = create_world(); diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index a9a1736fa0..ca962c40b1 100644 --- a/crates/bevy_sprite/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -1,6 +1,4 @@ -#![allow(deprecated)] - -use crate::{Sprite, TextureAtlas}; +use crate::Sprite; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -15,7 +13,7 @@ use bevy_transform::components::{GlobalTransform, Transform}; /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](crate::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Clone, Debug, Default)] pub struct SpriteBundle { /// Specifies the rendering properties of the sprite, such as color tint and flip. @@ -33,37 +31,3 @@ pub struct SpriteBundle { /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering pub view_visibility: ViewVisibility, } - -/// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred -/// to as a `TextureAtlas`) or for animated sprites. -/// -/// Note: -/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component. -/// -/// Check the following examples for usage: -/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) -/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs) -/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) -#[deprecated( - since = "0.14.0", - note = "Use `TextureAtlas` alongside a `SpriteBundle` instead" -)] -#[derive(Bundle, Clone, Debug, Default)] -pub struct SpriteSheetBundle { - /// Specifies the rendering properties of the sprite, such as color tint and flip. - pub sprite: Sprite, - /// The local transform of the sprite, relative to its parent. - pub transform: Transform, - /// The absolute transform of the sprite. This should generally not be written to directly. - pub global_transform: GlobalTransform, - /// The sprite sheet base texture - pub texture: Handle, - /// The sprite sheet texture atlas, allowing to draw a custom section of `texture`. - pub atlas: TextureAtlas, - /// User indication of whether an entity is visible - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, -} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 152b612bd1..a907d85dd5 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -23,10 +23,6 @@ mod texture_slice; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[allow(deprecated)] - #[doc(hidden)] - pub use crate::bundle::SpriteSheetBundle; - #[doc(hidden)] pub use crate::{ bundle::SpriteBundle, @@ -82,7 +78,7 @@ pub enum SpriteSystem { /// /// Right now, this is used for `Text`. #[derive(Component, Reflect, Clone, Copy, Debug, Default)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct SpriteSource; /// A convenient alias for `With>`, for use with diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 8bce6f1cb6..7ef45ec298 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -52,7 +52,7 @@ use crate::Material2dBindGroupId; /// /// It wraps a [`Handle`] to differentiate from the 3d pipelines which use the handles directly as components #[derive(Default, Clone, Component, Debug, Reflect, PartialEq, Eq, Deref, DerefMut)] -#[reflect(Default, Component)] +#[reflect(Default, Component, Debug, PartialEq)] pub struct Mesh2dHandle(pub Handle); impl From> for Mesh2dHandle { @@ -871,7 +871,7 @@ impl RenderCommand

for DrawMesh2d { ); } RenderMeshBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); + pass.draw(vertex_buffer_slice.range, batch_range.clone()); } } RenderCommandResult::Success diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 43bd10c622..83af6c3ed9 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -56,7 +56,7 @@ impl Plugin for Wireframe2dPlugin { /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Wireframe2d; /// Sets the color of the [`Wireframe2d`] of the entity it is attached to. @@ -65,7 +65,7 @@ pub struct Wireframe2d; /// /// This overrides the [`Wireframe2dConfig::default_color`]. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Wireframe2dColor { pub color: Color, } @@ -75,11 +75,11 @@ pub struct Wireframe2dColor { /// /// This requires the [`Wireframe2dPlugin`] to be enabled. #[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct NoWireframe2d; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] -#[reflect(Resource)] +#[reflect(Resource, Debug, Default)] pub struct Wireframe2dConfig { /// Whether to show wireframes for all 2D meshes. /// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component. diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index f47ca22a5a..15fd90b084 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -2,13 +2,13 @@ //! 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. -use std::cmp::Ordering; +use std::cmp::Reverse; use crate::{Sprite, TextureAtlas, TextureAtlasLayout}; use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_ecs::prelude::*; -use bevy_math::{prelude::*, FloatExt}; +use bevy_math::{prelude::*, FloatExt, FloatOrd}; use bevy_picking::backend::prelude::*; use bevy_render::prelude::*; use bevy_transform::prelude::*; @@ -43,12 +43,11 @@ pub fn sprite_picking( >, mut output: EventWriter, ) { - let mut sorted_sprites: Vec<_> = sprite_query.iter().collect(); - sorted_sprites.sort_by(|a, b| { - (b.4.translation().z) - .partial_cmp(&a.4.translation().z) - .unwrap_or(Ordering::Equal) - }); + let mut sorted_sprites: Vec<_> = sprite_query + .iter() + .filter(|x| !x.4.affine().is_nan()) + .collect(); + sorted_sprites.sort_by_key(|x| Reverse(FloatOrd(x.4.translation().z))); let primary_window = primary_window.get_single().ok(); diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index a0ce5071ec..13e37ee56c 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -9,8 +9,7 @@ use crate::TextureSlicer; /// /// This is commonly used as a component within [`SpriteBundle`](crate::bundle::SpriteBundle). #[derive(Component, Debug, Default, Clone, Reflect)] -#[reflect(Component, Default)] -#[repr(C)] +#[reflect(Component, Default, Debug)] pub struct Sprite { /// The sprite's color tint pub color: Color, @@ -43,7 +42,7 @@ impl Sprite { /// Controls how the image is altered when scaled. #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug)] pub enum ImageScaleMode { /// The texture will be cut in 9 slices, keeping the texture in proportions on resize Sliced(TextureSlicer), @@ -62,6 +61,7 @@ pub enum ImageScaleMode { /// How a sprite is positioned relative to its [`Transform`](bevy_transform::components::Transform). /// It defaults to `Anchor::Center`. #[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)] +#[reflect(Component, Default, Debug, PartialEq)] #[doc(alias = "pivot")] pub enum Anchor { #[default] diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index ea3c67b640..70e10e5914 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -1,6 +1,8 @@ use bevy_asset::{Asset, AssetId, Assets, Handle}; use bevy_ecs::component::Component; +use bevy_ecs::reflect::ReflectComponent; use bevy_math::{URect, UVec2}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::texture::Image; use bevy_utils::HashMap; @@ -19,7 +21,6 @@ use bevy_utils::HashMap; #[derive(Asset, Reflect, Debug, Clone)] #[reflect(Debug)] pub struct TextureAtlasLayout { - // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer pub size: UVec2, /// The specific areas of the atlas where each texture can be found pub textures: Vec, @@ -142,6 +143,7 @@ impl TextureAtlasLayout { /// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs) /// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) #[derive(Component, Default, Debug, Clone, Reflect)] +#[reflect(Component, Default, Debug)] pub struct TextureAtlas { /// Texture atlas layout handle pub layout: Handle, diff --git a/crates/bevy_state/src/app.rs b/crates/bevy_state/src/app.rs index f550e8daa0..4541a1cd5d 100644 --- a/crates/bevy_state/src/app.rs +++ b/crates/bevy_state/src/app.rs @@ -117,7 +117,7 @@ impl AppExtStates for SubApp { .init_resource::>() .add_event::>(); let schedule = self.get_schedule_mut(StateTransition).expect( - "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling init_state?" + "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?" ); S::register_state(schedule); self.world_mut().send_event(StateTransitionEvent { @@ -146,7 +146,9 @@ impl AppExtStates for SubApp { .contains_resource::>>() { self.add_event::>(); - let schedule = self.get_schedule_mut(StateTransition).unwrap(); + let schedule = self.get_schedule_mut(StateTransition).expect( + "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_computed_state?" + ); S::register_computed_state_systems(schedule); let state = self .world() @@ -172,7 +174,9 @@ impl AppExtStates for SubApp { { self.init_resource::>(); self.add_event::>(); - let schedule = self.get_schedule_mut(StateTransition).unwrap(); + let schedule = self.get_schedule_mut(StateTransition).expect( + "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_sub_state?" + ); S::register_sub_state_systems(schedule); let state = self .world() diff --git a/crates/bevy_state/src/commands.rs b/crates/bevy_state/src/commands.rs new file mode 100644 index 0000000000..036e35d603 --- /dev/null +++ b/crates/bevy_state/src/commands.rs @@ -0,0 +1,30 @@ +use bevy_ecs::{system::Commands, world::World}; +use bevy_utils::tracing::debug; + +use crate::state::{FreelyMutableState, NextState}; + +/// Extension trait for [`Commands`] adding `bevy_state` helpers. +pub trait CommandsStatesExt { + /// Sets the next state the app should move to. + /// + /// Internally this schedules a command that updates the [`NextState`](crate::prelude::NextState) + /// resource with `state`. + /// + /// Note that commands introduce sync points to the ECS schedule, so modifying `NextState` + /// directly may be more efficient depending on your use-case. + fn set_state(&mut self, state: S); +} + +impl CommandsStatesExt for Commands<'_, '_> { + fn set_state(&mut self, state: S) { + self.queue(move |w: &mut World| { + let mut next = w.resource_mut::>(); + if let NextState::Pending(prev) = &*next { + if *prev != state { + debug!("overwriting next state {:?} with {:?}", prev, state); + } + } + next.set(state); + }); + } +} diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index e8dc8264ef..c2aba9c24d 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -34,6 +34,8 @@ #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods pub mod app; +/// Provides extension methods for [`Commands`](bevy_ecs::prelude::Commands). +pub mod commands; /// Provides definitions for the runtime conditions that interact with the state system pub mod condition; /// Provides definitions for the basic traits required by the state system @@ -42,6 +44,10 @@ pub mod state; /// Provides [`StateScoped`](crate::state_scoped::StateScoped) and /// [`clear_state_scoped_entities`](crate::state_scoped::clear_state_scoped_entities) for managing lifetime of entities. pub mod state_scoped; +#[cfg(feature = "bevy_app")] +/// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with methods for registering +/// state-scoped events. +pub mod state_scoped_events; #[cfg(feature = "bevy_reflect")] /// Provides definitions for the basic traits required by the state system @@ -55,6 +61,8 @@ pub mod prelude { #[doc(hidden)] pub use crate::app::AppExtStates; #[doc(hidden)] + pub use crate::commands::CommandsStatesExt; + #[doc(hidden)] pub use crate::condition::*; #[cfg(feature = "bevy_reflect")] #[doc(hidden)] @@ -67,4 +75,7 @@ pub mod prelude { }; #[doc(hidden)] pub use crate::state_scoped::StateScoped; + #[cfg(feature = "bevy_app")] + #[doc(hidden)] + pub use crate::state_scoped_events::StateScopedEventsAppExt; } diff --git a/crates/bevy_state/src/state/resources.rs b/crates/bevy_state/src/state/resources.rs index c9f4279634..e4d5804ebf 100644 --- a/crates/bevy_state/src/state/resources.rs +++ b/crates/bevy_state/src/state/resources.rs @@ -52,7 +52,7 @@ use bevy_reflect::prelude::ReflectDefault; #[cfg_attr( feature = "bevy_reflect", derive(bevy_reflect::Reflect), - reflect(Resource) + reflect(Resource, Debug, PartialEq) )] pub struct State(pub(crate) S); @@ -118,7 +118,7 @@ impl Deref for State { #[cfg_attr( feature = "bevy_reflect", derive(bevy_reflect::Reflect), - reflect(Resource, Default) + reflect(Resource, Default, Debug) )] pub enum NextState { /// No state transition is pending diff --git a/crates/bevy_state/src/state_scoped_events.rs b/crates/bevy_state/src/state_scoped_events.rs new file mode 100644 index 0000000000..5e2c3f68c4 --- /dev/null +++ b/crates/bevy_state/src/state_scoped_events.rs @@ -0,0 +1,109 @@ +use std::marker::PhantomData; + +use bevy_app::{App, SubApp}; +use bevy_ecs::{ + event::{Event, EventReader, Events}, + system::{Commands, Resource}, + world::World, +}; +use bevy_utils::HashMap; + +use crate::state::{FreelyMutableState, OnExit, StateTransitionEvent}; + +fn clear_event_queue(w: &mut World) { + if let Some(mut queue) = w.get_resource_mut::>() { + queue.clear(); + } +} + +#[derive(Resource)] +struct StateScopedEvents { + cleanup_fns: HashMap>, +} + +impl StateScopedEvents { + fn add_event(&mut self, state: S) { + self.cleanup_fns + .entry(state) + .or_default() + .push(clear_event_queue::); + } + + fn cleanup(&self, w: &mut World, state: S) { + let Some(fns) = self.cleanup_fns.get(&state) else { + return; + }; + for callback in fns { + (*callback)(w); + } + } +} + +impl Default for StateScopedEvents { + fn default() -> Self { + Self { + cleanup_fns: HashMap::default(), + } + } +} + +fn cleanup_state_scoped_event( + mut c: Commands, + mut transitions: EventReader>, +) { + let Some(transition) = transitions.read().last() else { + return; + }; + if transition.entered == transition.exited { + return; + } + let Some(exited) = transition.exited.clone() else { + return; + }; + + c.queue(move |w: &mut World| { + w.resource_scope::, ()>(|w, events| { + events.cleanup(w, exited); + }); + }); +} + +fn add_state_scoped_event_impl( + app: &mut SubApp, + _p: PhantomData, + state: S, +) { + if !app.world().contains_resource::>() { + app.init_resource::>(); + } + app.add_event::(); + app.world_mut() + .resource_mut::>() + .add_event::(state.clone()); + app.add_systems(OnExit(state), cleanup_state_scoped_event::); +} + +/// Extension trait for [`App`] adding methods for registering state scoped events. +pub trait StateScopedEventsAppExt { + /// Adds an [`Event`] that is automatically cleaned up when leaving the specified `state`. + /// + /// Note that event cleanup is ordered ambiguously relative to [`StateScoped`](crate::prelude::StateScoped) entity + /// cleanup and the [`OnExit`] schedule for the target state. All of these (state scoped + /// entities and events cleanup, and `OnExit`) occur within schedule [`StateTransition`](crate::prelude::StateTransition) + /// and system set `StateTransitionSteps::ExitSchedules`. + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self; +} + +impl StateScopedEventsAppExt for App { + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + add_state_scoped_event_impl(self.main_mut(), PhantomData::, state); + self + } +} + +impl StateScopedEventsAppExt for SubApp { + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + add_state_scoped_event_impl(self, PhantomData::, state); + self + } +} diff --git a/crates/bevy_text/src/bounds.rs b/crates/bevy_text/src/bounds.rs index 57b8d95f1a..2dc5a27a49 100644 --- a/crates/bevy_text/src/bounds.rs +++ b/crates/bevy_text/src/bounds.rs @@ -1,5 +1,6 @@ use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_math::Vec2; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; /// The maximum width and height of text. The text will wrap according to the specified size. @@ -10,7 +11,7 @@ use bevy_reflect::Reflect; /// reliable limit if it is necessary to contain the text strictly in the bounds. Currently this /// component is mainly useful for text wrapping only. #[derive(Component, Copy, Clone, Debug, Reflect)] -#[reflect(Component)] +#[reflect(Component, Default, Debug)] pub struct TextBounds { /// The maximum width of text in logical pixels. /// If `None`, the width is unbounded. diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4c379e61e5..0b638cdb1d 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -123,7 +123,8 @@ impl Plugin for TextPlugin { .ambiguous_with(CameraUpdateSystem), remove_dropped_font_atlas_sets, ), - ); + ) + .add_systems(Last, trim_cosmic_cache); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems( diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 7ac4609627..103b37d4ea 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -1,7 +1,12 @@ use std::sync::Arc; use bevy_asset::{AssetId, Assets}; -use bevy_ecs::{component::Component, entity::Entity, reflect::ReflectComponent, system::Resource}; +use bevy_ecs::{ + component::Component, + entity::Entity, + reflect::ReflectComponent, + system::{ResMut, Resource}, +}; use bevy_math::{UVec2, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::texture::Image; @@ -307,7 +312,7 @@ impl TextPipeline { /// /// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`]. #[derive(Component, Clone, Default, Debug, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct TextLayoutInfo { /// Scaled and positioned glyphs in screenspace pub glyphs: Vec, @@ -407,3 +412,13 @@ fn buffer_dimensions(buffer: &Buffer) -> Vec2 { Vec2::new(width.ceil(), height).ceil() } + +/// Discards stale data cached in `FontSystem`. +pub(crate) fn trim_cosmic_cache(mut pipeline: ResMut) { + // A trim age of 2 was found to reduce frame time variance vs age of 1 when tested with dynamic text. + // See https://github.com/bevyengine/bevy/pull/15037 + // + // We assume only text updated frequently benefits from the shape cache (e.g. animated text, or + // text that is dynamically measured for UI). + pipeline.font_system_mut().shape_run_cache.trim(2); +} diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 9172b6896e..0982592c84 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -27,7 +27,7 @@ impl Default for CosmicBuffer { /// /// It contains all of the text value and styling information. #[derive(Component, Debug, Clone, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Text { /// The text's sections pub sections: Vec, diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index bf3f257884..4e02105bfe 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -7,22 +7,22 @@ use bevy_ecs::{ system::EntityCommands, world::{Command, EntityWorldMut, World}, }; -use bevy_hierarchy::{PushChild, RemoveParent}; +use bevy_hierarchy::{AddChild, RemoveParent}; -/// Command similar to [`PushChild`], but updating the child transform to keep +/// Command similar to [`AddChild`], but updating the child transform to keep /// it at the same [`GlobalTransform`]. /// /// You most likely want to use [`BuildChildrenTransformExt::set_parent_in_place`] /// method on [`EntityCommands`] instead. -pub struct PushChildInPlace { +pub struct AddChildInPlace { /// Parent entity to add the child to. pub parent: Entity, /// Child entity to add. pub child: Entity, } -impl Command for PushChildInPlace { +impl Command for AddChildInPlace { fn apply(self, world: &mut World) { - let hierarchy_command = PushChild { + let hierarchy_command = AddChild { child: self.child, parent: self.parent, }; @@ -91,13 +91,13 @@ pub trait BuildChildrenTransformExt { impl BuildChildrenTransformExt for EntityCommands<'_> { fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self { let child = self.id(); - self.commands().add(PushChildInPlace { child, parent }); + self.commands().queue(AddChildInPlace { child, parent }); self } fn remove_parent_in_place(&mut self) -> &mut Self { let child = self.id(); - self.commands().add(RemoveParentInPlace { child }); + self.commands().queue(RemoveParentInPlace { child }); self } } @@ -105,7 +105,7 @@ impl BuildChildrenTransformExt for EntityCommands<'_> { impl BuildChildrenTransformExt for EntityWorldMut<'_> { fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self { let child = self.id(); - self.world_scope(|world| PushChildInPlace { child, parent }.apply(world)); + self.world_scope(|world| AddChildInPlace { child, parent }.apply(world)); self } diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index dd8f4ca08f..0b37756796 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -6,6 +6,8 @@ use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_math::{Affine3A, Dir3, Isometry3d, Mat4, Quat, Vec3, Vec3A}; #[cfg(feature = "bevy-support")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +#[cfg(all(feature = "bevy-support", feature = "serialize"))] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// [`GlobalTransform`] is an affine transformation from entity-local coordinates to worldspace coordinates. /// @@ -39,7 +41,11 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg_attr( feature = "bevy-support", derive(Component, Reflect), - reflect(Component, Default, PartialEq) + reflect(Component, Default, PartialEq, Debug) +)] +#[cfg_attr( + all(feature = "bevy-support", feature = "serialize"), + reflect(Serialize, Deserialize) )] pub struct GlobalTransform(Affine3A); diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index f0d8901745..9202b0618c 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -3,7 +3,7 @@ use super::GlobalTransform; use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_math::{Affine3A, Dir3, Isometry3d, Mat3, Mat4, Quat, Vec3}; #[cfg(feature = "bevy-support")] -use bevy_reflect::{prelude::*, Reflect}; +use bevy_reflect::prelude::*; use std::ops::Mul; /// Describe the position of an entity. If the entity has a parent, the position is relative @@ -38,7 +38,11 @@ use std::ops::Mul; #[cfg_attr( feature = "bevy-support", derive(Component, Reflect), - reflect(Component, Default, PartialEq) + reflect(Component, Default, PartialEq, Debug) +)] +#[cfg_attr( + all(feature = "bevy-support", feature = "serialize"), + reflect(Serialize, Deserialize) )] pub struct Transform { /// Position of the entity. In 2d, the last value of the `Vec3` is used for z-ordering. diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index f32a19dc42..d6113f3c09 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -477,7 +477,7 @@ mod test { app.world_mut() .spawn(TransformBundle::IDENTITY) - .push_children(&[child]); + .add_children(&[child]); std::mem::swap( &mut *app.world_mut().get_mut::(child).unwrap(), &mut *temp.get_mut::(grandchild).unwrap(), diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 387d47c06c..59f75f1b3c 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -42,7 +42,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// - [`ButtonBundle`](crate::node_bundles::ButtonBundle) which includes this component /// - [`RelativeCursorPosition`] to obtain the position of the cursor relative to current node #[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -76,7 +76,7 @@ impl Default for Interaction { /// /// The component is updated when it is in the same entity with [`Node`]. #[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -101,7 +101,7 @@ impl RelativeCursorPosition { /// Describes whether the node should block interactions with lower nodes #[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -352,12 +352,12 @@ pub(crate) fn pick_rounded_rect( size: Vec2, border_radius: ResolvedBorderRadius, ) -> bool { - let s = point.signum(); - let r = (border_radius.top_left * (1. - s.x) * (1. - s.y) - + border_radius.top_right * (1. + s.x) * (1. - s.y) - + border_radius.bottom_right * (1. + s.x) * (1. + s.y) - + border_radius.bottom_left * (1. - s.x) * (1. + s.y)) - / 4.; + let [top, bottom] = if point.x < 0. { + [border_radius.top_left, border_radius.bottom_left] + } else { + [border_radius.top_right, border_radius.bottom_right] + }; + let r = if point.y < 0. { top } else { bottom }; let corner_to_point = point.abs() - 0.5 * size; let q = corner_to_point + r; diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index 610fc8fb7a..bd0164bbcc 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -14,7 +14,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// such as logical pixels, percentages, or automatically determined values. #[derive(Copy, Clone, Debug, Reflect)] -#[reflect(Default, PartialEq)] +#[reflect(Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -249,7 +249,7 @@ impl Val { /// ``` #[derive(Copy, Clone, PartialEq, Debug, Reflect)] -#[reflect(Default, PartialEq)] +#[reflect(Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index b529df6f30..269af520b1 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -76,7 +76,7 @@ pub struct UiLayoutSystemRemovedComponentParam<'w, 's> { #[doc(hidden)] #[derive(Default)] pub struct UiLayoutSystemBuffers { - interned_root_notes: Vec>, + interned_root_nodes: Vec>, resized_windows: EntityHashSet, camera_layout_info: EntityHashMap, } @@ -122,7 +122,7 @@ pub fn ui_layout_system( #[cfg(feature = "bevy_text")] mut text_pipeline: ResMut, ) { let UiLayoutSystemBuffers { - interned_root_notes, + interned_root_nodes, resized_windows, camera_layout_info, } = &mut *buffers; @@ -147,7 +147,7 @@ pub fn ui_layout_system( size, resized, scale_factor: scale_factor * ui_scale.0, - root_nodes: interned_root_notes.pop().unwrap_or_default(), + root_nodes: interned_root_nodes.pop().unwrap_or_default(), } }; @@ -280,7 +280,7 @@ pub fn ui_layout_system( } camera.root_nodes.clear(); - interned_root_notes.push(camera.root_nodes); + interned_root_nodes.push(camera.root_nodes); } fn update_uinode_geometry_recursive( diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 948e52ebfa..f55e875de2 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -21,6 +21,7 @@ pub mod widget; pub mod picking_backend; use bevy_derive::{Deref, DerefMut}; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; #[cfg(feature = "bevy_text")] mod accessibility; @@ -100,6 +101,7 @@ pub enum UiSystem { /// A multiplier to fixed-sized ui values. /// **Note:** This will only affect fixed ui values like [`Val::Px`] #[derive(Debug, Reflect, Resource, Deref, DerefMut)] +#[reflect(Resource, Debug, Default)] pub struct UiScale(pub f32); impl Default for UiScale { diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index b70010f327..a093cf13cf 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - //! This module contains basic node bundles used to build UIs #[cfg(feature = "bevy_text")] @@ -14,7 +12,6 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_ecs::bundle::Bundle; use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility}; -use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] use bevy_text::{ BreakLineOn, CosmicBuffer, JustifyText, Text, TextLayoutInfo, TextSection, TextStyle, @@ -67,7 +64,7 @@ pub struct NodeBundle { /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Debug, Default)] pub struct ImageBundle { /// Describes the logical size of the node @@ -110,56 +107,6 @@ pub struct ImageBundle { pub z_index: ZIndex, } -/// A UI node that is a texture atlas sprite -/// -/// # Extra behaviours -/// -/// You may add the following components to enable additional behaviours -/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// -/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component. -#[deprecated( - since = "0.14.0", - note = "Use `TextureAtlas` alongside `ImageBundle` instead" -)] -#[derive(Bundle, Debug, Default)] -pub struct AtlasImageBundle { - /// Describes the logical size of the node - pub node: Node, - /// Styles which control the layout (size and position) of the node and its children - /// In some cases these styles also affect how the node drawn/painted. - pub style: Style, - /// The calculated size based on the given image - pub calculated_size: ContentSize, - /// The image of the node - pub image: UiImage, - /// A handle to the texture atlas to use for this Ui Node - pub texture_atlas: TextureAtlas, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The size of the image in pixels - /// - /// This component is set automatically - pub image_size: UiImageSize, - /// The transform of the node - /// - /// This component is automatically managed by the UI layout system. - /// To alter the position of the `AtlasImageBundle`, use the properties of the [`Style`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - #[cfg(feature = "bevy_text")] /// A UI node that is text /// @@ -269,7 +216,7 @@ where /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Clone, Debug)] pub struct ButtonBundle { /// Describes the logical size of the node diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index cd9c66727a..0b646367aa 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -25,7 +25,7 @@ use ui_texture_slice_pipeline::UiTextureSlicerPlugin; use crate::graph::{NodeUi, SubGraphUi}; use crate::{ BackgroundColor, BorderColor, CalculatedClip, DefaultUiCamera, Display, Node, Outline, Style, - TargetCamera, UiImage, UiScale, Val, + TargetCamera, UiAntiAlias, UiImage, UiScale, Val, }; use bevy_app::prelude::*; @@ -260,8 +260,7 @@ pub fn extract_uinode_background_colors( uinode.border_radius.top_right, uinode.border_radius.bottom_right, uinode.border_radius.bottom_left, - ] - .map(|r| r * ui_scale.0); + ]; extracted_uinodes.uinodes.insert( entity, @@ -329,25 +328,31 @@ pub fn extract_uinode_images( continue; } - let (rect, atlas_scaling) = match atlas { - Some(atlas) => { - let Some(layout) = texture_atlases.get(&atlas.layout) else { - // Atlas not present in assets resource (should this warn the user?) - continue; - }; - let mut atlas_rect = layout.textures[atlas.index].as_rect(); - let atlas_scaling = uinode.size() / atlas_rect.size(); - atlas_rect.min *= atlas_scaling; - atlas_rect.max *= atlas_scaling; - (atlas_rect, Some(atlas_scaling)) + let atlas_rect = atlas + .and_then(|s| s.texture_rect(&texture_atlases)) + .map(|r| r.as_rect()); + + let mut rect = match (atlas_rect, image.rect) { + (None, None) => Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + (None, Some(image_rect)) => image_rect, + (Some(atlas_rect), None) => atlas_rect, + (Some(atlas_rect), Some(mut image_rect)) => { + image_rect.min += atlas_rect.min; + image_rect.max += atlas_rect.min; + image_rect } - None => ( - Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - None, - ), + }; + + let atlas_scaling = if atlas_rect.is_some() || image.rect.is_some() { + let atlas_scaling = uinode.size() / rect.size(); + rect.min *= atlas_scaling; + rect.max *= atlas_scaling; + Some(atlas_scaling) + } else { + None }; let ui_logical_viewport_size = camera_query @@ -381,8 +386,7 @@ pub fn extract_uinode_images( uinode.border_radius.top_right, uinode.border_radius.bottom_right, uinode.border_radius.bottom_left, - ] - .map(|r| r * ui_scale.0); + ]; extracted_uinodes.uinodes.insert( commands.spawn_empty().id(), @@ -519,8 +523,7 @@ pub fn extract_uinode_borders( uinode.border_radius.top_right, uinode.border_radius.bottom_right, uinode.border_radius.bottom_left, - ] - .map(|r| r * ui_scale.0); + ]; let border_radius = clamp_radius(border_radius, uinode.size(), border.into()); @@ -605,13 +608,15 @@ pub fn extract_default_ui_camera_view( mut commands: Commands, mut transparent_render_phases: ResMut>, ui_scale: Extract>, - query: Extract, With)>>>, + query: Extract< + Query<(Entity, &Camera, Option<&UiAntiAlias>), Or<(With, With)>>, + >, mut live_entities: Local, ) { live_entities.clear(); let scale = ui_scale.0.recip(); - for (entity, camera) in &query { + for (entity, camera, ui_anti_alias) in &query { // ignore inactive cameras if !camera.is_active { continue; @@ -657,9 +662,12 @@ pub fn extract_default_ui_camera_view( color_grading: Default::default(), }) .id(); - commands + let entity_commands = commands .get_or_spawn(entity) .insert(DefaultCameraView(default_camera_view)); + if let Some(ui_anti_alias) = ui_anti_alias { + entity_commands.insert(*ui_anti_alias); + } transparent_render_phases.insert_or_clear(entity); live_entities.insert(entity); @@ -834,13 +842,14 @@ pub fn queue_uinodes( ui_pipeline: Res, mut pipelines: ResMut>, mut transparent_render_phases: ResMut>, - mut views: Query<(Entity, &ExtractedView)>, + mut views: Query<(Entity, &ExtractedView, Option<&UiAntiAlias>)>, pipeline_cache: Res, draw_functions: Res>, ) { let draw_function = draw_functions.read().id::(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let Ok((view_entity, view)) = views.get_mut(extracted_uinode.camera_entity) else { + let Ok((view_entity, view, ui_anti_alias)) = views.get_mut(extracted_uinode.camera_entity) + else { continue; }; @@ -851,7 +860,10 @@ pub fn queue_uinodes( let pipeline = pipelines.specialize( &pipeline_cache, &ui_pipeline, - UiPipelineKey { hdr: view.hdr }, + UiPipelineKey { + hdr: view.hdr, + anti_alias: matches!(ui_anti_alias, None | Some(UiAntiAlias::On)), + }, ); transparent_phase.add(TransparentUi { draw_function, diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index e31dc3bcfb..715a1984b9 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -48,6 +48,7 @@ impl FromWorld for UiPipeline { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct UiPipelineKey { pub hdr: bool, + pub anti_alias: bool, } impl SpecializedRenderPipeline for UiPipeline { @@ -73,7 +74,11 @@ impl SpecializedRenderPipeline for UiPipeline { VertexFormat::Float32x2, ], ); - let shader_defs = Vec::new(); + let shader_defs = if key.anti_alias { + vec!["ANTI_ALIAS".into()] + } else { + Vec::new() + }; RenderPipelineDescriptor { vertex: VertexState { diff --git a/crates/bevy_ui/src/render/ui.wgsl b/crates/bevy_ui/src/render/ui.wgsl index 91d22067a2..ad82783d7f 100644 --- a/crates/bevy_ui/src/render/ui.wgsl +++ b/crates/bevy_ui/src/render/ui.wgsl @@ -150,11 +150,15 @@ fn draw(in: VertexOutput, texture_color: vec4) -> vec4 { // outside the outside edge, or inside the inner edge have positive signed distance. let border_distance = max(external_distance, -internal_distance); +#ifdef ANTI_ALIAS // At external edges with no border, `border_distance` is equal to zero. // This select statement ensures we only perform anti-aliasing where a non-zero width border // is present, otherwise an outline about the external boundary would be drawn even without // a border. let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance); +#else + let t = 1.0 - step(0.0, border_distance); +#endif // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here. return vec4(color.rgb, saturate(color.a * t)); @@ -165,7 +169,13 @@ fn draw_background(in: VertexOutput, texture_color: vec4) -> vec4 { // When drawing the background only draw the internal area and not the border. let internal_distance = sd_inset_rounded_box(in.point, in.size, in.radius, in.border); + +#ifdef ANTI_ALIAS let t = antialias(internal_distance); +#else + let t = 1.0 - step(0.0, internal_distance); +#endif + return vec4(color.rgb, saturate(color.a * t)); } diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index bcdccf15fc..1a3cf489ac 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -8,6 +8,7 @@ use bevy_ecs::{ system::lifetimeless::{Read, SRes}, system::*, }; +use bevy_hierarchy::Parent; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; use bevy_render::{ extract_component::ExtractComponentPlugin, @@ -327,7 +328,7 @@ impl RenderCommand

for DrawUiMaterialNode { } pub struct ExtractedUiMaterialNode { - pub stack_index: usize, + pub stack_index: u32, pub transform: Mat4, pub rect: Rect, pub border: [f32; 4], @@ -352,10 +353,10 @@ impl Default for ExtractedUiMaterialNodes { } } +#[allow(clippy::too_many_arguments)] pub fn extract_ui_material_nodes( mut extracted_uinodes: ResMut>, materials: Extract>>, - ui_stack: Extract>, default_ui_camera: Extract, uinode_query: Extract< Query< @@ -368,12 +369,14 @@ pub fn extract_ui_material_nodes( &ViewVisibility, Option<&CalculatedClip>, Option<&TargetCamera>, + Option<&Parent>, ), Without, >, >, windows: Extract>>, ui_scale: Extract>, + node_query: Extract>, ) { let ui_logical_viewport_size = windows .get_single() @@ -386,61 +389,58 @@ pub fn extract_ui_material_nodes( // If there is only one camera, we use it as default let default_single_camera = default_ui_camera.get(); - for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((entity, uinode, style, transform, handle, view_visibility, clip, camera)) = - uinode_query.get(*entity) - { - let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) - else { - continue; - }; - - // skip invisible nodes - if !view_visibility.get() { - continue; - } - - // Skip loading materials - if !materials.contains(handle) { - continue; - } - - // Both vertical and horizontal percentage border values are calculated based on the width of the parent node - // - let parent_width = uinode.size().x; - let left = - resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size) - / uinode.size().x; - let right = resolve_border_thickness( - style.border.right, - parent_width, - ui_logical_viewport_size, - ) / uinode.size().x; - let top = - resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size) - / uinode.size().y; - let bottom = resolve_border_thickness( - style.border.bottom, - parent_width, - ui_logical_viewport_size, - ) / uinode.size().y; - - extracted_uinodes.uinodes.insert( - entity, - ExtractedUiMaterialNode { - stack_index, - transform: transform.compute_matrix(), - material: handle.id(), - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - border: [left, right, top, bottom], - clip: clip.map(|clip| clip.clip), - camera_entity, - }, - ); + for (entity, uinode, style, transform, handle, view_visibility, clip, camera, maybe_parent) in + uinode_query.iter() + { + let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else { + continue; }; + + // skip invisible nodes + if !view_visibility.get() { + continue; + } + + // Skip loading materials + if !materials.contains(handle) { + continue; + } + + // Both vertical and horizontal percentage border values are calculated based on the width of the parent node + // + let parent_width = maybe_parent + .and_then(|parent| node_query.get(parent.get()).ok()) + .map(|parent_node| parent_node.size().x) + .unwrap_or(ui_logical_viewport_size.x); + + let left = + resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size) + / uinode.size().x; + let right = + resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size) + / uinode.size().x; + let top = + resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size) + / uinode.size().y; + let bottom = + resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size) + / uinode.size().y; + + extracted_uinodes.uinodes.insert( + entity, + ExtractedUiMaterialNode { + stack_index: uinode.stack_index, + transform: transform.compute_matrix(), + material: handle.id(), + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + border: [left, right, top, bottom], + clip: clip.map(|clip| clip.clip), + camera_entity, + }, + ); } } 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 ce60087cf2..c712d78386 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -275,11 +275,20 @@ pub fn extract_ui_texture_slices( continue; } - let atlas_rect = atlas.and_then(|atlas| { - texture_atlases - .get(&atlas.layout) - .map(|layout| layout.textures[atlas.index].as_rect()) - }); + let atlas_rect = atlas + .and_then(|s| s.texture_rect(&texture_atlases)) + .map(|r| r.as_rect()); + + let atlas_rect = match (atlas_rect, image.rect) { + (None, None) => None, + (None, Some(image_rect)) => Some(image_rect), + (Some(atlas_rect), None) => Some(atlas_rect), + (Some(atlas_rect), Some(mut image_rect)) => { + image_rect.min += atlas_rect.min; + image_rect.max += atlas_rect.min; + Some(image_rect) + } + }; extracted_ui_slicers.slices.insert( commands.spawn_empty().id(), diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index b0555961eb..81e9280f23 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -24,7 +24,7 @@ use thiserror::Error; /// to obtain the cursor position relative to this node /// - [`Interaction`](crate::Interaction) to obtain the interaction state of this node #[derive(Component, Debug, Copy, Clone, PartialEq, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Node { /// The order of the node in the UI layout. /// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices. @@ -169,7 +169,7 @@ impl Default for Node { /// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way. #[derive(Component, Clone, PartialEq, Debug, Reflect)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default, PartialEq, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1684,7 +1684,7 @@ pub enum GridPlacementError { /// /// This serves as the "fill" color. #[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1711,7 +1711,7 @@ impl> From for BackgroundColor { /// The border color of the UI node. #[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1737,7 +1737,7 @@ impl Default for BorderColor { } #[derive(Component, Copy, Clone, Default, Debug, PartialEq, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1822,7 +1822,7 @@ impl Outline { /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct UiImage { /// The tint color used to draw the image. /// @@ -1837,6 +1837,12 @@ pub struct UiImage { pub flip_x: bool, /// Whether the image should be flipped along its y-axis pub flip_y: bool, + /// An optional rectangle representing the region of the image to render, instead of rendering + /// the full image. This is an easy one-off alternative to using a [`TextureAtlas`](bevy_sprite::TextureAtlas). + /// + /// When used with a [`TextureAtlas`](bevy_sprite::TextureAtlas), the rect + /// is offset by the atlas's minimal (top-left) corner position. + pub rect: Option, } impl Default for UiImage { @@ -1856,6 +1862,7 @@ impl Default for UiImage { texture: TRANSPARENT_IMAGE_HANDLE, flip_x: false, flip_y: false, + rect: None, } } } @@ -1879,6 +1886,7 @@ impl UiImage { color, flip_x: false, flip_y: false, + rect: None, } } @@ -1902,6 +1910,12 @@ impl UiImage { self.flip_y = true; self } + + #[must_use] + pub const fn with_rect(mut self, rect: Rect) -> Self { + self.rect = Some(rect); + self + } } impl From> for UiImage { @@ -1912,7 +1926,7 @@ impl From> for UiImage { /// The calculated clip of the node #[derive(Component, Default, Copy, Clone, Debug, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct CalculatedClip { /// The rect of the clip pub clip: Rect, @@ -1926,9 +1940,10 @@ pub struct CalculatedClip { /// appear in the UI hierarchy. In such a case, the last node to be added to its parent /// will appear in front of its siblings. /// + /// Nodes without this component will be treated as if they had a value of [`ZIndex(0)`]. -#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Default, Reflect)] -#[reflect(Component, Default)] +#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct ZIndex(pub i32); /// `GlobalZIndex` allows a node anywhere in the UI hierarchy to escape the implicit draw ordering of the UI's layout tree and @@ -1937,8 +1952,8 @@ pub struct ZIndex(pub i32); /// Nodes with a `GlobalZIndex` of less than 0 will be drawn below nodes without a `GlobalZIndex` or nodes with a greater `GlobalZIndex`. /// /// If two Nodes have the same `GlobalZIndex`, the node with the greater [`ZIndex`] will be drawn on top. -#[derive(Component, Copy, Clone, Debug, PartialEq, Eq, Default, Reflect)] -#[reflect(Component, Default)] +#[derive(Component, Copy, Clone, Debug, Default, PartialEq, Eq, Reflect)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct GlobalZIndex(pub i32); /// Used to add rounded corners to a UI node. You can set a UI node to have uniformly @@ -1982,7 +1997,7 @@ pub struct GlobalZIndex(pub i32); /// /// #[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)] -#[reflect(Component, PartialEq, Default)] +#[reflect(Component, PartialEq, Default, Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -2286,6 +2301,7 @@ mod tests { /// /// Optional if there is only one camera in the world. Required otherwise. #[derive(Component, Clone, Debug, Reflect, Eq, PartialEq)] +#[reflect(Component, Debug, PartialEq)] pub struct TargetCamera(pub Entity); impl TargetCamera { @@ -2360,3 +2376,29 @@ impl<'w, 's> DefaultUiCamera<'w, 's> { }) } } + +/// Marker for controlling whether Ui is rendered with or without anti-aliasing +/// in a camera. By default, Ui is always anti-aliased. +/// +/// ``` +/// use bevy_core_pipeline::prelude::*; +/// use bevy_ecs::prelude::*; +/// use bevy_ui::prelude::*; +/// +/// fn spawn_camera(mut commands: Commands) { +/// commands.spawn(( +/// Camera2dBundle::default(), +/// // This will cause all Ui in this camera to be rendered without +/// // anti-aliasing +/// UiAntiAlias::Off, +/// )); +/// } +/// ``` +#[derive(Component, Clone, Copy, Default, Debug, Reflect, Eq, PartialEq)] +pub enum UiAntiAlias { + /// UI will render with anti-aliasing + #[default] + On, + /// UI will render without anti-aliasing + Off, +} diff --git a/crates/bevy_ui/src/widget/button.rs b/crates/bevy_ui/src/widget/button.rs index d28c9ce5cd..dbe1405e92 100644 --- a/crates/bevy_ui/src/widget/button.rs +++ b/crates/bevy_ui/src/widget/button.rs @@ -5,5 +5,5 @@ use bevy_reflect::Reflect; /// Marker struct for buttons #[derive(Component, Debug, Default, Clone, Copy, PartialEq, Eq, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug, PartialEq)] pub struct Button; diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index cf04384404..dc846dbe4b 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -12,7 +12,7 @@ use taffy::{MaybeMath, MaybeResolve}; /// /// This component is updated automatically by [`update_image_content_size_system`] #[derive(Component, Debug, Copy, Clone, Default, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct UiImageSize { /// The size of the image's texture /// diff --git a/crates/bevy_ui/src/widget/label.rs b/crates/bevy_ui/src/widget/label.rs index 5b4561ac34..4aa7f7e36b 100644 --- a/crates/bevy_ui/src/widget/label.rs +++ b/crates/bevy_ui/src/widget/label.rs @@ -5,5 +5,5 @@ use bevy_reflect::Reflect; /// Marker struct for labels #[derive(Component, Debug, Default, Clone, Copy, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Label; diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 515dae7f05..66de4f01b5 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -26,7 +26,7 @@ use taffy::style::AvailableSpace; /// /// Used internally by [`measure_text_system`] and [`text_system`] to schedule text for processing. #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct TextFlags { /// If set a new measure function for the text node will be created needs_new_measure_func: bool, diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 52f995961a..7f83ec17c9 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -9,11 +9,16 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] +default = ["std"] +std = ["alloc", "tracing/std", "ahash/std"] +alloc = [] detailed_trace = [] [dependencies] -ahash = "0.8.7" -tracing = { version = "0.1", default-features = false, features = ["std"] } +ahash = { version = "0.8.7", default-features = false, features = [ + "runtime-rng", +] } +tracing = { version = "0.1", default-features = false } web-time = { version = "1.1" } hashbrown = { version = "0.14.2", features = ["serde"] } bevy_utils_proc_macros = { version = "0.15.0-dev", path = "macros" } diff --git a/crates/bevy_utils/src/futures.rs b/crates/bevy_utils/src/futures.rs index ab425cee3a..6a4f9ff9cc 100644 --- a/crates/bevy_utils/src/futures.rs +++ b/crates/bevy_utils/src/futures.rs @@ -1,5 +1,5 @@ //! Utilities for working with [`Future`]s. -use std::{ +use core::{ future::Future, pin::Pin, task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, @@ -36,15 +36,15 @@ pub fn check_ready(future: &mut F) -> Option { } } -unsafe fn noop_clone(_data: *const ()) -> RawWaker { +fn noop_clone(_data: *const ()) -> RawWaker { noop_raw_waker() } -unsafe fn noop(_data: *const ()) {} +fn noop(_data: *const ()) {} const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); fn noop_raw_waker() -> RawWaker { - RawWaker::new(std::ptr::null(), &NOOP_WAKER_VTABLE) + RawWaker::new(core::ptr::null(), &NOOP_WAKER_VTABLE) } fn noop_waker() -> Waker { diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index b53bcd3e2e..3d231cf0cf 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -4,12 +4,16 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] +#![cfg_attr(not(feature = "std"), no_std)] //! General utilities for first-party [Bevy] engine crates. //! //! [Bevy]: https://bevyengine.org/ //! +#[cfg(feature = "alloc")] +extern crate alloc; + /// The utilities prelude. /// /// This includes the most common types in this crate, re-exported for your convenience. @@ -19,7 +23,7 @@ pub mod prelude { pub mod futures; mod short_names; -pub use short_names::get_short_name; +pub use short_names::ShortName; pub mod synccell; pub mod syncunsafecell; @@ -37,8 +41,10 @@ pub use parallel_queue::*; pub use tracing; pub use web_time::{Duration, Instant, SystemTime, SystemTimeError, TryFromFloatSecsError}; -use hashbrown::hash_map::RawEntryMut; -use std::{ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use core::{ any::TypeId, fmt::Debug, hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, @@ -46,6 +52,7 @@ use std::{ mem::ManuallyDrop, ops::Deref, }; +use hashbrown::hash_map::RawEntryMut; #[cfg(not(target_arch = "wasm32"))] mod conditional_send { @@ -66,11 +73,12 @@ pub use conditional_send::*; /// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. Wasm), /// futures aren't Send. -pub trait ConditionalSendFuture: std::future::Future + ConditionalSend {} -impl ConditionalSendFuture for T {} +pub trait ConditionalSendFuture: core::future::Future + ConditionalSend {} +impl ConditionalSendFuture for T {} /// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection. -pub type BoxedFuture<'a, T> = std::pin::Pin + 'a>>; +#[cfg(feature = "alloc")] +pub type BoxedFuture<'a, T> = core::pin::Pin + 'a>>; /// A shortcut alias for [`hashbrown::hash_map::Entry`]. pub type Entry<'a, K, V, S = BuildHasherDefault> = hashbrown::hash_map::Entry<'a, K, V, S>; @@ -192,7 +200,7 @@ impl PartialEq for Hashed { } impl Debug for Hashed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Hashed") .field("hash", &self.hash) .field("value", &self.value) @@ -276,85 +284,6 @@ impl PreHashMapExt for PreHashMap Self::Hasher { - EntityHasher::default() - } -} - -/// A very fast hash that is only designed to work on generational indices -/// like `Entity`. It will panic if attempting to hash a type containing -/// non-u64 fields. -/// -/// This is heavily optimized for typical cases, where you have mostly live -/// entities, and works particularly well for contiguous indices. -/// -/// If you have an unusual case -- say all your indices are multiples of 256 -/// or most of the entities are dead generations -- then you might want also to -/// try [`AHasher`] for a slower hash computation but fewer lookup conflicts. -#[derive(Debug, Default)] -pub struct EntityHasher { - hash: u64, -} - -impl Hasher for EntityHasher { - #[inline] - fn finish(&self) -> u64 { - self.hash - } - - fn write(&mut self, _bytes: &[u8]) { - panic!("can only hash u64 using EntityHasher"); - } - - #[inline] - fn write_u64(&mut self, bits: u64) { - // SwissTable (and thus `hashbrown`) cares about two things from the hash: - // - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item - // - H2: high 7 bits are used to SIMD optimize hash collision probing - // For more see - - // This hash function assumes that the entity ids are still well-distributed, - // so for H1 leaves the entity id alone in the low bits so that id locality - // will also give memory locality for things spawned together. - // For H2, take advantage of the fact that while multiplication doesn't - // spread entropy to the low bits, it's incredibly good at spreading it - // upward, which is exactly where we need it the most. - - // While this does include the generation in the output, it doesn't do so - // *usefully*. H1 won't care until you have over 3 billion entities in - // the table, and H2 won't care until something hits generation 33 million. - // Thus the comment suggesting that this is best for live entities, - // where there won't be generation conflicts where it would matter. - - // The high 32 bits of this are ⅟φ for Fibonacci hashing. That works - // particularly well for hashing for the same reason as described in - // - // It loses no information because it has a modular inverse. - // (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.) - // - // The low 32 bits make that part of the just product a pass-through. - const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001; - - // This is `(MAGIC * index + generation) << 32 + index`, in a single instruction. - self.hash = bits.wrapping_mul(UPPER_PHI); - } -} - -/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing. -/// Iteration order only depends on the order of insertions and deletions. -pub type EntityHashMap = hashbrown::HashMap; - -/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing. -/// Iteration order only depends on the order of insertions and deletions. -pub type EntityHashSet = hashbrown::HashSet; - /// A specialized hashmap type with Key of [`TypeId`] /// Iteration order only depends on the order of insertions and deletions. pub type TypeIdMap = hashbrown::HashMap; @@ -496,7 +425,7 @@ mod tests { fn fast_typeid_hash() { struct Hasher; - impl std::hash::Hasher for Hasher { + impl core::hash::Hasher for Hasher { fn finish(&self) -> u64 { 0 } @@ -509,8 +438,11 @@ mod tests { Hash::hash(&TypeId::of::<()>(), &mut Hasher); } + #[cfg(feature = "alloc")] #[test] fn stable_hash_within_same_program_execution() { + use alloc::vec::Vec; + let mut map_1 = HashMap::new(); let mut map_2 = HashMap::new(); for i in 1..10 { diff --git a/crates/bevy_utils/src/parallel_queue.rs b/crates/bevy_utils/src/parallel_queue.rs index 61741da4f2..01377c7573 100644 --- a/crates/bevy_utils/src/parallel_queue.rs +++ b/crates/bevy_utils/src/parallel_queue.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, ops::DerefMut}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::vec::Vec; + +use core::{cell::RefCell, ops::DerefMut}; use thread_local::ThreadLocal; /// A cohesive set of thread-local values of a given type. @@ -56,6 +59,7 @@ where } } +#[cfg(feature = "alloc")] impl Parallel> { /// Collect all enqueued items from all threads and appends them to the end of a /// single Vec. diff --git a/crates/bevy_utils/src/short_names.rs b/crates/bevy_utils/src/short_names.rs index 60a7b70d40..d14548b681 100644 --- a/crates/bevy_utils/src/short_names.rs +++ b/crates/bevy_utils/src/short_names.rs @@ -1,60 +1,115 @@ -/// Shortens a type name to remove all module paths. +/// Lazily shortens a type name to remove all module paths. /// /// The short name of a type is its full name as returned by /// [`std::any::type_name`], but with the prefix of all paths removed. For /// example, the short name of `alloc::vec::Vec>` /// would be `Vec>`. -pub fn get_short_name(full_name: &str) -> String { - // Generics result in nested paths within <..> blocks. - // Consider "bevy_render::camera::camera::extract_cameras". - // To tackle this, we parse the string from left to right, collapsing as we go. - let mut index: usize = 0; - let end_of_string = full_name.len(); - let mut parsed_name = String::new(); +/// +/// Shortening is performed lazily without allocation. +#[cfg_attr( + feature = "alloc", + doc = r#" To get a [`String`] from this type, use the [`to_string`](`alloc::string::ToString::to_string`) method."# +)] +/// +/// # Examples +/// +/// ```rust +/// # use bevy_utils::ShortName; +/// # +/// # mod foo { +/// # pub mod bar { +/// # pub struct Baz; +/// # } +/// # } +/// // Baz +/// let short_name = ShortName::of::(); +/// ``` +#[derive(Clone, Copy)] +pub struct ShortName<'a>(pub &'a str); - while index < end_of_string { - let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); - - // Collapse everything up to the next special character, - // then skip over it - if let Some(special_character_index) = rest_of_string.find(|c: char| { - (c == ' ') - || (c == '<') - || (c == '>') - || (c == '(') - || (c == ')') - || (c == '[') - || (c == ']') - || (c == ',') - || (c == ';') - }) { - let segment_to_collapse = rest_of_string - .get(0..special_character_index) - .unwrap_or_default(); - parsed_name += collapse_type_name(segment_to_collapse); - // Insert the special character - let special_character = - &rest_of_string[special_character_index..=special_character_index]; - parsed_name.push_str(special_character); - - match special_character { - ">" | ")" | "]" - if rest_of_string[special_character_index + 1..].starts_with("::") => - { - parsed_name.push_str("::"); - // Move the index past the "::" - index += special_character_index + 3; - } - // Move the index just past the special character - _ => index += special_character_index + 1, - } - } else { - // If there are no special characters left, we're done! - parsed_name += collapse_type_name(rest_of_string); - index = end_of_string; - } +impl ShortName<'static> { + /// Gets a shortened version of the name of the type `T`. + pub fn of() -> Self { + Self(core::any::type_name::()) + } +} + +impl<'a> ShortName<'a> { + /// Gets the original name before shortening. + pub const fn original(&self) -> &'a str { + self.0 + } +} + +impl<'a> From<&'a str> for ShortName<'a> { + fn from(value: &'a str) -> Self { + Self(value) + } +} + +impl<'a> core::fmt::Debug for ShortName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let &ShortName(full_name) = self; + // Generics result in nested paths within <..> blocks. + // Consider "bevy_render::camera::camera::extract_cameras". + // To tackle this, we parse the string from left to right, collapsing as we go. + let mut index: usize = 0; + let end_of_string = full_name.len(); + + while index < end_of_string { + let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); + + // Collapse everything up to the next special character, + // then skip over it + if let Some(special_character_index) = rest_of_string.find(|c: char| { + (c == ' ') + || (c == '<') + || (c == '>') + || (c == '(') + || (c == ')') + || (c == '[') + || (c == ']') + || (c == ',') + || (c == ';') + }) { + let segment_to_collapse = rest_of_string + .get(0..special_character_index) + .unwrap_or_default(); + + f.write_str(collapse_type_name(segment_to_collapse))?; + + // Insert the special character + let special_character = + &rest_of_string[special_character_index..=special_character_index]; + + f.write_str(special_character)?; + + match special_character { + ">" | ")" | "]" + if rest_of_string[special_character_index + 1..].starts_with("::") => + { + f.write_str("::")?; + // Move the index past the "::" + index += special_character_index + 3; + } + // Move the index just past the special character + _ => index += special_character_index + 1, + } + } else { + // If there are no special characters left, we're done! + f.write_str(collapse_type_name(rest_of_string))?; + index = end_of_string; + } + } + + Ok(()) + } +} + +impl<'a> core::fmt::Display for ShortName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + ::fmt(self, f) } - parsed_name } #[inline(always)] @@ -75,88 +130,85 @@ fn collapse_type_name(string: &str) -> &str { } } -#[cfg(test)] +#[cfg(all(test, feature = "alloc"))] mod name_formatting_tests { - use super::get_short_name; + use super::ShortName; #[test] fn trivial() { - assert_eq!(get_short_name("test_system"), "test_system"); + assert_eq!(ShortName("test_system").to_string(), "test_system"); } #[test] fn path_separated() { assert_eq!( - get_short_name("bevy_prelude::make_fun_game"), - "make_fun_game".to_string() + ShortName("bevy_prelude::make_fun_game").to_string(), + "make_fun_game" ); } #[test] fn tuple_type() { assert_eq!( - get_short_name("(String, String)"), - "(String, String)".to_string() + ShortName("(String, String)").to_string(), + "(String, String)" ); } #[test] fn array_type() { - assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string()); + assert_eq!(ShortName("[i32; 3]").to_string(), "[i32; 3]"); } #[test] fn trivial_generics() { - assert_eq!(get_short_name("a"), "a".to_string()); + assert_eq!(ShortName("a").to_string(), "a"); } #[test] fn multiple_type_parameters() { - assert_eq!(get_short_name("a"), "a".to_string()); + assert_eq!(ShortName("a").to_string(), "a"); } #[test] fn enums() { - assert_eq!(get_short_name("Option::None"), "Option::None".to_string()); + assert_eq!(ShortName("Option::None").to_string(), "Option::None"); + assert_eq!(ShortName("Option::Some(2)").to_string(), "Option::Some(2)"); assert_eq!( - get_short_name("Option::Some(2)"), - "Option::Some(2)".to_string() - ); - assert_eq!( - get_short_name("bevy_render::RenderSet::Prepare"), - "RenderSet::Prepare".to_string() + ShortName("bevy_render::RenderSet::Prepare").to_string(), + "RenderSet::Prepare" ); } #[test] fn generics() { assert_eq!( - get_short_name("bevy_render::camera::camera::extract_cameras"), - "extract_cameras".to_string() + ShortName("bevy_render::camera::camera::extract_cameras").to_string(), + "extract_cameras" ); } #[test] fn nested_generics() { assert_eq!( - get_short_name("bevy::mad_science::do_mad_science, bavy::TypeSystemAbuse>"), - "do_mad_science, TypeSystemAbuse>".to_string() + ShortName("bevy::mad_science::do_mad_science, bavy::TypeSystemAbuse>").to_string(), + "do_mad_science, TypeSystemAbuse>" ); } #[test] fn sub_path_after_closing_bracket() { assert_eq!( - get_short_name("bevy_asset::assets::Assets::asset_event_system"), - "Assets::asset_event_system".to_string() + ShortName("bevy_asset::assets::Assets::asset_event_system").to_string(), + "Assets::asset_event_system" ); assert_eq!( - get_short_name("(String, String)::default"), - "(String, String)::default".to_string() + ShortName("(String, String)::default").to_string(), + "(String, String)::default" ); assert_eq!( - get_short_name("[i32; 16]::default"), - "[i32; 16]::default".to_string() + ShortName("[i32; 16]::default").to_string(), + "[i32; 16]::default" ); } } diff --git a/crates/bevy_utils/src/synccell.rs b/crates/bevy_utils/src/synccell.rs index e99c97b7d3..628fb2756d 100644 --- a/crates/bevy_utils/src/synccell.rs +++ b/crates/bevy_utils/src/synccell.rs @@ -2,7 +2,7 @@ //! //! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html -use std::ptr; +use core::ptr; /// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation, /// which should replace this one entirely. diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index caf97be6f4..2334024fba 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -1,4 +1,3 @@ -#![allow(deprecated)] use std::path::PathBuf; use bevy_ecs::{entity::Entity, event::Event}; @@ -10,7 +9,6 @@ use bevy_input::{ }; use bevy_math::{IVec2, Vec2}; use bevy_reflect::Reflect; -use smol_str::SmolStr; #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; @@ -190,22 +188,6 @@ pub struct CursorLeft { pub window: Entity, } -/// An event that is sent whenever a window receives a character from the OS or underlying system. -#[deprecated(since = "0.14.0", note = "Use `KeyboardInput` instead.")] -#[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub struct ReceivedCharacter { - /// Window that received the character. - pub window: Entity, - /// Received character. - pub char: SmolStr, -} - /// A Input Method Editor event. /// /// This event is the translated version of the `WindowEvent::Ime` from the `winit` crate. @@ -239,8 +221,7 @@ pub enum Ime { }, /// Notifies when the IME was enabled. /// - /// After this event, you will receive events `Ime::Preedit` and `Ime::Commit`, - /// and stop receiving events [`ReceivedCharacter`]. + /// After this event, you will receive events `Ime::Preedit` and `Ime::Commit`. Enabled { /// Window that received the event. window: Entity, @@ -440,7 +421,6 @@ pub enum WindowEvent { CursorMoved(CursorMoved), FileDragAndDrop(FileDragAndDrop), Ime(Ime), - ReceivedCharacter(ReceivedCharacter), RequestRedraw(RequestRedraw), WindowBackendScaleFactorChanged(WindowBackendScaleFactorChanged), WindowCloseRequested(WindowCloseRequested), @@ -498,11 +478,6 @@ impl From for WindowEvent { Self::Ime(e) } } -impl From for WindowEvent { - fn from(e: ReceivedCharacter) -> Self { - Self::ReceivedCharacter(e) - } -} impl From for WindowEvent { fn from(e: RequestRedraw) -> Self { Self::RequestRedraw(e) diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 49d6e96f40..e6e06ab49f 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -34,12 +34,10 @@ pub use window::*; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[allow(deprecated)] #[doc(hidden)] pub use crate::{ - CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, MonitorSelection, - ReceivedCharacter, Window, WindowMoved, WindowPlugin, WindowPosition, - WindowResizeConstraints, + CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, MonitorSelection, Window, + WindowMoved, WindowPlugin, WindowPosition, WindowResizeConstraints, }; } @@ -92,7 +90,6 @@ pub struct WindowPlugin { impl Plugin for WindowPlugin { fn build(&self, app: &mut App) { // User convenience events - #[allow(deprecated)] app.add_event::() .add_event::() .add_event::() @@ -104,7 +101,6 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() - .add_event::() .add_event::() .add_event::() .add_event::() @@ -145,7 +141,6 @@ impl Plugin for WindowPlugin { } // Register event types - #[allow(deprecated)] app.register_type::() .register_type::() .register_type::() @@ -156,7 +151,6 @@ impl Plugin for WindowPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_window/src/monitor.rs b/crates/bevy_window/src/monitor.rs index 64fafa89c1..756574596b 100644 --- a/crates/bevy_window/src/monitor.rs +++ b/crates/bevy_window/src/monitor.rs @@ -22,7 +22,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Component)] +#[reflect(Component, Debug)] pub struct Monitor { /// The name of the monitor pub name: Option, @@ -42,7 +42,7 @@ pub struct Monitor { /// A marker component for the primary monitor #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug)] pub struct PrimaryMonitor; impl Monitor { diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index eb8ca610fc..6e80ed7474 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -20,7 +20,7 @@ use bevy_utils::tracing::warn; /// with this component if [`primary_window`](crate::WindowPlugin::primary_window) /// is `Some`. #[derive(Default, Debug, Component, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Reflect)] -#[reflect(Component)] +#[reflect(Component, Debug, Default, PartialEq)] pub struct PrimaryWindow; /// Reference to a [`Window`], whether it be a direct link to a specific entity or @@ -124,7 +124,7 @@ impl NormalizedWindowRef { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Component, Default)] +#[reflect(Component, Default, Debug)] pub struct Window { /// The cursor options of this window. Cursor icons are set with the `Cursor` component on the /// window entity. @@ -227,7 +227,6 @@ pub struct Window { /// Should the window use Input Method Editor? /// /// If enabled, the window will receive [`Ime`](crate::Ime) events instead of - /// [`ReceivedCharacter`](crate::ReceivedCharacter) or /// `KeyboardInput` from `bevy_input`. /// /// IME should be enabled during text input, but not when you expect to get the exact key pressed. diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index b70720dece..afad99ab09 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -22,7 +22,6 @@ pub use winit::platform::android::activity as android_activity; use bevy_a11y::AccessibilityRequested; use bevy_app::{App, Last, Plugin}; use bevy_ecs::prelude::*; -#[allow(deprecated)] use bevy_window::{exit_on_all_closed, Window, WindowCreated}; pub use converters::convert_system_cursor_icon; pub use state::{CursorSource, CustomCursorCache, CustomCursorCacheKey, PendingCursor}; diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index f515513a32..4cff720878 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -24,10 +24,9 @@ use winit::event::{DeviceEvent, DeviceId, StartCause, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::window::WindowId; -#[allow(deprecated)] use bevy_window::{ - AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, - RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed, + AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, RequestRedraw, + Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed, WindowEvent as BevyWindowEvent, WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, WindowThemeChanged, }; @@ -272,14 +271,6 @@ impl ApplicationHandler for WinitAppRunnerState { // properly releasing keys when the window loses focus. if !(is_synthetic && event.state.is_pressed()) { // Process the keyboard input event, as long as it's not a synthetic key press. - if event.state.is_pressed() { - if let Some(char) = &event.text { - let char = char.clone(); - #[allow(deprecated)] - self.bevy_window_events - .send(ReceivedCharacter { window, char }); - } - } self.bevy_window_events .send(converters::convert_keyboard_input(event, window)); } @@ -716,9 +707,6 @@ impl WinitAppRunnerState { BevyWindowEvent::Ime(e) => { world.send_event(e); } - BevyWindowEvent::ReceivedCharacter(e) => { - world.send_event(e); - } BevyWindowEvent::RequestRedraw(e) => { world.send_event(e); } diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index b456aba7ce..521a198157 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -205,3 +205,11 @@ sudo eopkg it wayland-devel libxkbcommon-devel ``` Compiling with clang is also possible - replace the `g++` package with `llvm-clang` + +## [FreeBSD](https://www.freebsd.org/) + +It is necessary to have the hgame module loaded in order to satisfy gli-rs. It will still throw an error, but the program should run successfully. You can make sure the kernel module is loaded on start up by adding the following line to /boot/loader.conf: + +```sh +hgame_load="YES" +``` diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index 3735fcf300..a603ced251 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -2,7 +2,7 @@ use bevy::{ core_pipeline::{ - bloom::{BloomCompositeMode, BloomSettings}, + bloom::{Bloom, BloomCompositeMode}, tonemapping::Tonemapping, }, prelude::*, @@ -32,7 +32,7 @@ fn setup( tonemapping: Tonemapping::TonyMcMapface, // 2. Using a tonemapper that desaturates to white is recommended ..default() }, - BloomSettings::default(), // 3. Enable bloom for the camera + Bloom::default(), // 3. Enable bloom for the camera )); // Sprite @@ -78,120 +78,113 @@ fn setup( // ------------------------------------------------------------------------------------------------ fn update_bloom_settings( - mut camera: Query<(Entity, Option<&mut BloomSettings>), With>, + mut camera: Query<(Entity, Option<&mut Bloom>), With>, mut text: Query<&mut Text>, mut commands: Commands, keycode: Res>, time: Res